cml/
validate.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::features::{Feature, FeatureSet};
6use crate::{
7    offer_to_all_would_duplicate, AnyRef, Availability, Capability, CapabilityClause,
8    CapabilityFromRef, CapabilityId, Child, Collection, ConfigKey, ConfigType, ConfigValueType,
9    DependencyType, DictionaryRef, Document, Environment, EnvironmentExtends, EnvironmentRef,
10    Error, EventScope, Expose, ExposeFromRef, ExposeToRef, FromClause, Name, Offer, OfferFromRef,
11    OfferToRef, OneOrMany, Program, RegistrationRef, Rights, RootDictionaryRef, SourceAvailability,
12    Use, UseFromRef,
13};
14use cm_types::IterablePath;
15use directed_graph::DirectedGraph;
16use itertools::Either;
17use std::collections::{BTreeMap, HashMap, HashSet};
18use std::hash::Hash;
19use std::path::Path;
20use std::{fmt, iter};
21
22#[derive(Default, Clone)]
23pub struct CapabilityRequirements<'a> {
24    pub must_offer: &'a [OfferToAllCapability<'a>],
25    pub must_use: &'a [MustUseRequirement<'a>],
26}
27
28#[derive(PartialEq)]
29pub enum MustUseRequirement<'a> {
30    Protocol(&'a str),
31}
32
33impl<'a> MustUseRequirement<'a> {
34    fn name(&self) -> &str {
35        match self {
36            MustUseRequirement::Protocol(name) => name,
37        }
38    }
39}
40
41#[derive(PartialEq, Clone)]
42pub enum OfferToAllCapability<'a> {
43    Dictionary(&'a str),
44    Protocol(&'a str),
45}
46
47impl<'a> OfferToAllCapability<'a> {
48    pub fn name(&self) -> &'a str {
49        match self {
50            OfferToAllCapability::Dictionary(name) => name,
51            OfferToAllCapability::Protocol(name) => name,
52        }
53    }
54
55    pub fn offer_type(&self) -> &'static str {
56        match self {
57            OfferToAllCapability::Dictionary(_) => "Dictionary",
58            OfferToAllCapability::Protocol(_) => "Protocol",
59        }
60    }
61
62    pub fn offer_type_plural(&self) -> &'static str {
63        match self {
64            OfferToAllCapability::Dictionary(_) => "dictionaries",
65            OfferToAllCapability::Protocol(_) => "protocols",
66        }
67    }
68}
69
70pub fn offer_to_all_from_offer(value: &Offer) -> impl Iterator<Item = OfferToAllCapability<'_>> {
71    if let Some(protocol) = &value.protocol {
72        Either::Left(
73            protocol.iter().map(|protocol| OfferToAllCapability::Protocol(protocol.as_str())),
74        )
75    } else if let Some(dictionary) = &value.dictionary {
76        Either::Right(
77            dictionary
78                .iter()
79                .map(|dictionary| OfferToAllCapability::Dictionary(dictionary.as_str())),
80        )
81    } else {
82        panic!("Expected a dictionary or a protocol");
83    }
84}
85
86/// Validates a given cml.
87pub(crate) fn validate_cml(
88    document: &Document,
89    file: Option<&Path>,
90    features: &FeatureSet,
91    capability_requirements: &CapabilityRequirements<'_>,
92) -> Result<(), Error> {
93    let mut ctx = ValidationContext::new(&document, features, capability_requirements);
94    let mut res = ctx.validate();
95    if let Err(Error::Validate { filename, .. }) = &mut res {
96        if let Some(file) = file {
97            *filename = Some(file.to_string_lossy().into_owned());
98        }
99    }
100    res
101}
102
103fn duplicate_capability_check<'a>(
104    duplicate_check: &mut HashSet<CapabilityId<'a>>,
105    capability_string: &str,
106    offered_to_all: &Vec<&'a Offer>,
107    filter: impl Fn(&&&Offer) -> bool,
108) -> Result<(), Error> {
109    let problem_capabilities = offered_to_all
110        .iter()
111        .filter(filter)
112        .map(|o| CapabilityId::from_offer_expose(*o))
113        .collect::<Result<Vec<Vec<CapabilityId<'a>>>, _>>()?
114        .into_iter()
115        .flatten()
116        .filter(|cap_id| !duplicate_check.insert((*cap_id).clone()))
117        .collect::<Vec<_>>();
118    if !problem_capabilities.is_empty() {
119        return Err(Error::validate(format!(
120            r#"{} {:?} offered to "all" multiple times"#,
121            capability_string,
122            problem_capabilities.iter().map(|p| format!("{p}")).collect::<Vec<_>>()
123        )));
124    }
125    Ok(())
126}
127
128fn offer_can_have_dependency(offer: &Offer) -> bool {
129    offer.directory.is_some() || offer.protocol.is_some() || offer.service.is_some()
130}
131
132fn offer_dependency(offer: &Offer) -> DependencyType {
133    offer.dependency.clone().unwrap_or(DependencyType::Strong)
134}
135struct ValidationContext<'a> {
136    document: &'a Document,
137    features: &'a FeatureSet,
138    capability_requirements: &'a CapabilityRequirements<'a>,
139    all_children: HashMap<&'a Name, &'a Child>,
140    all_collections: HashSet<&'a Name>,
141    all_storages: HashMap<&'a Name, &'a CapabilityFromRef>,
142    all_services: HashSet<&'a Name>,
143    all_protocols: HashSet<&'a Name>,
144    all_directories: HashSet<&'a Name>,
145    all_runners: HashSet<&'a Name>,
146    all_resolvers: HashSet<&'a Name>,
147    all_dictionaries: HashMap<&'a Name, &'a Capability>,
148    all_configs: HashSet<&'a Name>,
149    all_environment_names: HashSet<&'a Name>,
150    all_capability_names: HashSet<&'a Name>,
151    strong_dependencies: DirectedGraph<DependencyNode<'a>>,
152}
153
154// Facet key for fuchsia.test
155const TEST_FACET_KEY: &'static str = "fuchsia.test";
156
157// Facet key for deprecated-allowed-packages.
158const TEST_DEPRECATED_ALLOWED_PACKAGES_FACET_KEY: &'static str = "deprecated-allowed-packages";
159
160// Facet key for type.
161const TEST_TYPE_FACET_KEY: &'static str = "type";
162
163impl<'a> ValidationContext<'a> {
164    fn new(
165        document: &'a Document,
166        features: &'a FeatureSet,
167        capability_requirements: &'a CapabilityRequirements<'a>,
168    ) -> Self {
169        ValidationContext {
170            document,
171            features,
172            capability_requirements,
173            all_children: HashMap::new(),
174            all_collections: HashSet::new(),
175            all_storages: HashMap::new(),
176            all_services: HashSet::new(),
177            all_protocols: HashSet::new(),
178            all_directories: HashSet::new(),
179            all_runners: HashSet::new(),
180            all_resolvers: HashSet::new(),
181            all_dictionaries: HashMap::new(),
182            all_configs: HashSet::new(),
183            all_environment_names: HashSet::new(),
184            all_capability_names: HashSet::new(),
185            strong_dependencies: DirectedGraph::new(),
186        }
187    }
188
189    fn validate(&mut self) -> Result<(), Error> {
190        // Ensure child components, collections, and storage don't use the
191        // same name.
192        //
193        // We currently have the ability to distinguish between storage and
194        // children/collections based on context, but still enforce name
195        // uniqueness to give us flexibility in future.
196        let all_children_names =
197            self.document.all_children_names().into_iter().zip(iter::repeat("children"));
198        let all_collection_names =
199            self.document.all_collection_names().into_iter().zip(iter::repeat("collections"));
200        let all_storage_names =
201            self.document.all_storage_names().into_iter().zip(iter::repeat("storage"));
202        let all_runner_names =
203            self.document.all_runner_names().into_iter().zip(iter::repeat("runners"));
204        let all_resolver_names =
205            self.document.all_resolver_names().into_iter().zip(iter::repeat("resolvers"));
206        let all_environment_names =
207            self.document.all_environment_names().into_iter().zip(iter::repeat("environments"));
208        let all_dictionary_names =
209            self.document.all_dictionary_names().into_iter().zip(iter::repeat("dictionaries"));
210        ensure_no_duplicate_names(
211            all_children_names
212                .chain(all_collection_names)
213                .chain(all_storage_names)
214                .chain(all_runner_names)
215                .chain(all_resolver_names)
216                .chain(all_environment_names)
217                .chain(all_dictionary_names),
218        )?;
219
220        // Populate the sets of children and collections.
221        if let Some(children) = &self.document.children {
222            self.all_children = children.iter().map(|c| (&c.name, c)).collect();
223        }
224        self.all_collections = self.document.all_collection_names().into_iter().collect();
225        self.all_storages = self.document.all_storage_with_sources();
226        self.all_services = self.document.all_service_names().into_iter().collect();
227        self.all_protocols = self.document.all_protocol_names().into_iter().collect();
228        self.all_directories = self.document.all_directory_names().into_iter().collect();
229        self.all_runners = self.document.all_runner_names().into_iter().collect();
230        self.all_resolvers = self.document.all_resolver_names().into_iter().collect();
231        self.all_dictionaries = self.document.all_dictionaries().into_iter().collect();
232        self.all_configs = self.document.all_config_names().into_iter().collect();
233        self.all_environment_names = self.document.all_environment_names().into_iter().collect();
234        self.all_capability_names = self.document.all_capability_names();
235
236        // Validate "children".
237        if let Some(children) = &self.document.children {
238            for child in children {
239                self.validate_child(&child)?;
240            }
241        }
242
243        // Validate "collections".
244        if let Some(collections) = &self.document.collections {
245            for collection in collections {
246                self.validate_collection(&collection)?;
247            }
248        }
249
250        // Validate "capabilities".
251        if let Some(capabilities) = self.document.capabilities.as_ref() {
252            let mut used_ids = HashMap::new();
253            for capability in capabilities {
254                self.validate_capability(capability, &mut used_ids)?;
255            }
256        }
257
258        // Validate "use".
259        let mut uses_runner = false;
260        if let Some(uses) = self.document.r#use.as_ref() {
261            let mut used_ids = HashMap::new();
262            for use_ in uses.iter() {
263                self.validate_use(&use_, &mut used_ids)?;
264                if use_.runner.is_some() {
265                    uses_runner = true;
266                }
267            }
268        }
269
270        // Validate "expose".
271        if let Some(exposes) = self.document.expose.as_ref() {
272            let mut used_ids = HashMap::new();
273            let mut exposed_to_framework_ids = HashMap::new();
274            for expose in exposes.iter() {
275                self.validate_expose(&expose, &mut used_ids, &mut exposed_to_framework_ids)?;
276            }
277        }
278
279        // Validate "offer".
280        if let Some(offers) = self.document.offer.as_ref() {
281            let mut used_ids = HashMap::new();
282            let offered_to_all = offers
283                .iter()
284                .filter(|o| matches!(o.to, OneOrMany::One(OfferToRef::All)))
285                .filter(|o| o.protocol.is_some() || o.dictionary.is_some())
286                .collect::<Vec<&Offer>>();
287
288            let mut duplicate_check: HashSet<CapabilityId<'a>> = HashSet::new();
289
290            duplicate_capability_check(
291                &mut duplicate_check,
292                "Protocol(s)",
293                &offered_to_all,
294                |o| o.protocol.is_some(),
295            )?;
296            duplicate_capability_check(
297                &mut duplicate_check,
298                "Dictionary(s)",
299                &offered_to_all,
300                |o| o.dictionary.is_some(),
301            )?;
302
303            for offer in offers.iter() {
304                self.validate_offer(&offer, &mut used_ids, &offered_to_all)?;
305            }
306        }
307
308        if uses_runner {
309            // Component "use"s a runner. Ensure we don't also have a runner specified in "program",
310            // which would necessarily conflict.
311            self.validate_runner_not_specified(self.document.program.as_ref())?;
312        } else {
313            // Component doesn't "use" a runner. Ensure we don't have a component with a "program"
314            // block which fails to specify a runner.
315            self.validate_runner_specified(self.document.program.as_ref())?;
316        }
317
318        // Validate "environments".
319        if let Some(environments) = &self.document.environments {
320            for env in environments {
321                self.validate_environment(&env)?;
322            }
323        }
324
325        // Validate "config"
326        self.validate_config(&self.document.config)?;
327
328        // Check for dependency cycles
329        match self.strong_dependencies.topological_sort() {
330            Ok(_) => {}
331            Err(e) => {
332                return Err(Error::validate(format!(
333                    "Strong dependency cycles were found. Break the cycle by removing a dependency or marking an offer as weak. Cycles: {}", e.format_cycle())));
334            }
335        }
336
337        // Check that required offers are present
338        self.validate_required_offer_decls()?;
339
340        // Check that required use decls are present
341        self.validate_required_use_decls()?;
342
343        self.validate_facets()?;
344
345        Ok(())
346    }
347
348    fn get_test_facet(&self) -> Option<&serde_json::Value> {
349        match &self.document.facets {
350            Some(m) => m.get(TEST_FACET_KEY),
351            None => None,
352        }
353    }
354
355    fn validate_facets(&self) -> Result<(), Error> {
356        let test_facet_map = {
357            let test_facet = self.get_test_facet();
358            match &test_facet {
359                None => None,
360                Some(serde_json::Value::Object(m)) => Some(m),
361                Some(facet) => {
362                    return Err(Error::validate(format!(
363                        "'{TEST_FACET_KEY}' is not an object: {facet:?}"
364                    )))
365                }
366            }
367        };
368
369        let restrict_test_type = self.features.has(&Feature::RestrictTestTypeInFacet);
370        let enable_allow_non_hermetic_packages =
371            self.features.has(&Feature::EnableAllowNonHermeticPackagesFeature);
372
373        if restrict_test_type {
374            let test_type = test_facet_map.map(|m| m.get(TEST_TYPE_FACET_KEY)).flatten();
375            if test_type.is_some() {
376                return Err(Error::validate(format!(
377                    "'{}' is not a allowed in facets. Refer \
378https://fuchsia.dev/fuchsia-src/development/testing/components/test_runner_framework?hl=en#non-hermetic_tests \
379to run your test in the correct test realm.", TEST_TYPE_FACET_KEY)));
380            }
381        }
382
383        if enable_allow_non_hermetic_packages {
384            let allow_non_hermetic_packages = self.features.has(&Feature::AllowNonHermeticPackages);
385            let deprecated_allowed_packages = test_facet_map
386                .map_or(false, |m| m.contains_key(TEST_DEPRECATED_ALLOWED_PACKAGES_FACET_KEY));
387            if deprecated_allowed_packages && !allow_non_hermetic_packages {
388                return Err(Error::validate(format!(
389                    "restricted_feature '{}' should be present with facet '{}'",
390                    Feature::AllowNonHermeticPackages,
391                    TEST_DEPRECATED_ALLOWED_PACKAGES_FACET_KEY
392                )));
393            }
394            if allow_non_hermetic_packages && !deprecated_allowed_packages {
395                return Err(Error::validate(format!(
396                    "Remove restricted_feature '{}' as manifest does not contain facet '{}'",
397                    Feature::AllowNonHermeticPackages,
398                    TEST_DEPRECATED_ALLOWED_PACKAGES_FACET_KEY
399                )));
400            }
401        }
402        Ok(())
403    }
404
405    fn validate_child(&mut self, child: &'a Child) -> Result<(), Error> {
406        if let Some(resource) = child.url.resource() {
407            if resource.ends_with(".cml") {
408                return Err(Error::validate(format!(
409                    "child URL ends in .cml instead of .cm, \
410which is almost certainly a mistake: {}",
411                    child.url
412                )));
413            }
414        }
415
416        if let Some(environment_ref) = &child.environment {
417            match environment_ref {
418                EnvironmentRef::Named(environment_name) => {
419                    if !self.all_environment_names.contains(&environment_name) {
420                        return Err(Error::validate(format!(
421                            "\"{}\" does not appear in \"environments\"",
422                            &environment_name
423                        )));
424                    }
425                    let source = DependencyNode::Named(&environment_name);
426                    let target = DependencyNode::Named(&child.name);
427                    self.add_strong_dep(source, target);
428                }
429            }
430        }
431        Ok(())
432    }
433
434    fn validate_collection(&mut self, collection: &'a Collection) -> Result<(), Error> {
435        if collection.allow_long_names.is_some() {
436            self.features.check(Feature::AllowLongNames)?;
437        }
438        if let Some(environment_ref) = &collection.environment {
439            match environment_ref {
440                EnvironmentRef::Named(environment_name) => {
441                    if !self.all_environment_names.contains(&environment_name) {
442                        return Err(Error::validate(format!(
443                            "\"{}\" does not appear in \"environments\"",
444                            &environment_name
445                        )));
446                    }
447                    let source = DependencyNode::Named(&environment_name);
448                    let target = DependencyNode::Named(&collection.name);
449                    self.add_strong_dep(source, target);
450                }
451            }
452        }
453        Ok(())
454    }
455
456    fn validate_capability(
457        &mut self,
458        capability: &'a Capability,
459        used_ids: &mut HashMap<String, CapabilityId<'a>>,
460    ) -> Result<(), Error> {
461        if capability.directory.is_some() && capability.path.is_none() {
462            return Err(Error::validate("\"path\" should be present with \"directory\""));
463        }
464        if capability.directory.is_some() && capability.rights.is_none() {
465            return Err(Error::validate("\"rights\" should be present with \"directory\""));
466        }
467        if let Some(name) = capability.storage.as_ref() {
468            if capability.from.is_none() {
469                return Err(Error::validate("\"from\" should be present with \"storage\""));
470            }
471            if capability.path.is_some() {
472                return Err(Error::validate(
473                    "\"path\" can not be present with \"storage\", use \"backing_dir\"",
474                ));
475            }
476            if capability.backing_dir.is_none() {
477                return Err(Error::validate("\"backing_dir\" should be present with \"storage\""));
478            }
479            if capability.storage_id.is_none() {
480                return Err(Error::validate("\"storage_id\" should be present with \"storage\""));
481            }
482
483            // The storage capability depends on its backing dir.
484            let target = DependencyNode::Named(name);
485            let source = capability.from.as_ref().unwrap();
486            let names = vec![capability.backing_dir.as_ref().unwrap()];
487            for source in self.expand_source_dependencies(names, &source.into()) {
488                self.add_strong_dep(source, target);
489            }
490        }
491        if capability.runner.is_some() && capability.from.is_some() {
492            return Err(Error::validate("\"from\" should not be present with \"runner\""));
493        }
494        if capability.runner.is_some() && capability.path.is_none() {
495            return Err(Error::validate("\"path\" should be present with \"runner\""));
496        }
497        if capability.resolver.is_some() && capability.from.is_some() {
498            return Err(Error::validate("\"from\" should not be present with \"resolver\""));
499        }
500        if capability.resolver.is_some() && capability.path.is_none() {
501            return Err(Error::validate("\"path\" should be present with \"resolver\""));
502        }
503
504        if let Some(name) = capability.dictionary.as_ref() {
505            if capability.path.is_some() {
506                self.features.check(Feature::DynamicDictionaries)?;
507                // If `path` is set that means the dictionary is provided by the program,
508                // which implies a dependency from `self` to the dictionary declaration.
509                let target = DependencyNode::Named(name);
510                self.add_strong_dep(DependencyNode::Self_, target);
511            }
512        }
513        if capability.delivery.is_some() {
514            self.features.check(Feature::DeliveryType)?;
515        }
516        if let Some(from) = capability.from.as_ref() {
517            self.validate_component_child_ref("\"capabilities\" source", &AnyRef::from(from))?;
518        }
519
520        // Disallow multiple capability ids of the same name.
521        let capability_ids = CapabilityId::from_capability(capability)?;
522        for capability_id in capability_ids {
523            if used_ids.insert(capability_id.to_string(), capability_id.clone()).is_some() {
524                return Err(Error::validate(format!(
525                    "\"{}\" is a duplicate \"capability\" name",
526                    capability_id,
527                )));
528            }
529        }
530
531        Ok(())
532    }
533
534    fn validate_use(
535        &mut self,
536        use_: &'a Use,
537        used_ids: &mut HashMap<String, CapabilityId<'a>>,
538    ) -> Result<(), Error> {
539        use_.capability_type()?;
540        for checker in [
541            self.service_from_self_checker(use_),
542            self.protocol_from_self_checker(use_),
543            self.directory_from_self_checker(use_),
544            self.config_from_self_checker(use_),
545        ] {
546            checker.validate("used")?;
547        }
548
549        if use_.from == Some(UseFromRef::Debug) && use_.protocol.is_none() {
550            return Err(Error::validate("only \"protocol\" supports source from \"debug\""));
551        }
552        if use_.event_stream.is_some() && use_.availability.is_some() {
553            return Err(Error::validate("\"availability\" cannot be used with \"event_stream\""));
554        }
555        if use_.event_stream.is_none() && use_.filter.is_some() {
556            return Err(Error::validate("\"filter\" can only be used with \"event_stream\""));
557        }
558        if use_.storage.is_some() && use_.from.is_some() {
559            return Err(Error::validate("\"from\" cannot be used with \"storage\""));
560        }
561        if use_.runner.is_some() && use_.availability.is_some() {
562            return Err(Error::validate("\"availability\" cannot be used with \"runner\""));
563        }
564        if use_.from == Some(UseFromRef::Self_) && use_.event_stream.is_some() {
565            return Err(Error::validate("\"from: self\" cannot be used with \"event_stream\""));
566        }
567        if use_.from == Some(UseFromRef::Self_) && use_.runner.is_some() {
568            return Err(Error::validate("\"from: self\" cannot be used with \"runner\""));
569        }
570        if use_.availability == Some(Availability::SameAsTarget) {
571            return Err(Error::validate(
572                "\"availability: same_as_target\" cannot be used with use declarations",
573            ));
574        }
575        if let Some(UseFromRef::Dictionary(_)) = use_.from.as_ref() {
576            if use_.storage.is_some() {
577                return Err(Error::validate(
578                    "Dictionaries do not support \"storage\" capabilities",
579                ));
580            }
581            if use_.event_stream.is_some() {
582                return Err(Error::validate(
583                    "Dictionaries do not support \"event_stream\" capabilities",
584                ));
585            }
586        }
587        if let Some(config) = use_.config.as_ref() {
588            if use_.key == None {
589                return Err(Error::validate(format!("Config '{}' missing field 'key'", config)));
590            }
591            let _ = use_config_to_value_type(use_)?;
592            let availability = use_.availability.unwrap_or(Availability::Required);
593            if availability == Availability::Required && use_.config_default.is_some() {
594                return Err(Error::validate(format!(
595                    "Config '{}' is required and has a default value",
596                    config
597                )));
598            }
599        }
600
601        if let Some(source) = use_.from.as_ref() {
602            for source in self.expand_source_dependencies(use_.names(), &source.into()) {
603                let target = DependencyNode::Self_;
604                if use_.dependency.as_ref().unwrap_or(&DependencyType::Strong)
605                    == &DependencyType::Strong
606                {
607                    self.add_strong_dep(source, target);
608                }
609            }
610        }
611
612        // Disallow multiple capability ids of the same name.
613        let capability_ids = CapabilityId::from_use(use_)?;
614        for capability_id in capability_ids {
615            if used_ids.insert(capability_id.to_string(), capability_id.clone()).is_some() {
616                return Err(Error::validate(format!(
617                    "\"{}\" is a duplicate \"use\" target {}",
618                    capability_id,
619                    capability_id.type_str()
620                )));
621            }
622            let dir = capability_id.get_dir_path();
623
624            // Capability paths must not conflict with `/pkg`, or namespace generation might fail
625            let pkg_path = cm_types::NamespacePath::new("/pkg").unwrap();
626            if let Some(ref dir) = dir {
627                if dir.has_prefix(&pkg_path) {
628                    return Err(Error::validate(format!(
629                        "{} \"{}\" conflicts with the protected path \"/pkg\", please use this capability with a different path",
630                        capability_id.type_str(), capability_id,
631                    )));
632                }
633            }
634
635            // Validate that paths-based capabilities (service, directory, protocol)
636            // are not prefixes of each other.
637            for (_, used_id) in used_ids.iter() {
638                if capability_id == *used_id {
639                    continue;
640                }
641                let Some(ref dir) = dir else {
642                    continue;
643                };
644                let Some(used_dir) = used_id.get_dir_path() else {
645                    continue;
646                };
647
648                if match (used_id, &capability_id) {
649                    // Directories and storage can't be the same or partially overlap.
650                    (CapabilityId::UsedDirectory(_), CapabilityId::UsedStorage(_))
651                    | (CapabilityId::UsedStorage(_), CapabilityId::UsedDirectory(_))
652                    | (CapabilityId::UsedDirectory(_), CapabilityId::UsedDirectory(_))
653                    | (CapabilityId::UsedStorage(_), CapabilityId::UsedStorage(_)) => {
654                        dir.has_prefix(&used_dir) || used_dir.has_prefix(&dir)
655                    }
656
657                    // Protocols and services can't overlap with directories or storage.
658                    (CapabilityId::UsedDirectory(_), _)
659                    | (CapabilityId::UsedStorage(_), _)
660                    | (_, CapabilityId::UsedDirectory(_))
661                    | (_, CapabilityId::UsedStorage(_)) => {
662                        dir.has_prefix(&used_dir) || used_dir.has_prefix(&dir)
663                    }
664
665                    // Protocols and services containing directories may be same, but
666                    // partial overlap is disallowed.
667                    (_, _) => {
668                        *dir != used_dir && (dir.has_prefix(&used_dir) || used_dir.has_prefix(&dir))
669                    }
670                } {
671                    return Err(Error::validate(format!(
672                        "{} \"{}\" is a prefix of \"use\" target {} \"{}\"",
673                        used_id.type_str(),
674                        used_id,
675                        capability_id.type_str(),
676                        capability_id,
677                    )));
678                }
679            }
680        }
681
682        if let Some(_) = use_.directory.as_ref() {
683            // All directory "use" expressions must have directory rights.
684            match &use_.rights {
685                Some(rights) => self.validate_directory_rights(&rights)?,
686                None => {
687                    return Err(Error::validate("This use statement requires a `rights` field. Refer to: https://fuchsia.dev/go/components/directory#consumer."))
688                }
689            };
690        }
691
692        match (&use_.from, &use_.dependency) {
693            (Some(UseFromRef::Named(name)), _) if use_.service.is_some() => {
694                self.validate_component_child_or_collection_ref(
695                    "\"use\" source",
696                    &AnyRef::Named(name),
697                )?;
698            }
699            (Some(UseFromRef::Named(name)), _) => {
700                self.validate_component_child_or_capability_ref(
701                    "\"use\" source",
702                    &AnyRef::Named(name),
703                )?;
704            }
705            (_, Some(DependencyType::Weak)) => {
706                return Err(Error::validate(format!(
707                    "Only `use` from children can have dependency: \"weak\""
708                )));
709            }
710            _ => {}
711        }
712        Ok(())
713    }
714
715    fn validate_expose(
716        &self,
717        expose: &'a Expose,
718        used_ids: &mut HashMap<String, CapabilityId<'a>>,
719        exposed_to_framework_ids: &mut HashMap<String, CapabilityId<'a>>,
720    ) -> Result<(), Error> {
721        expose.capability_type()?;
722        for checker in [
723            self.service_from_self_checker(expose),
724            self.protocol_from_self_checker(expose),
725            self.directory_from_self_checker(expose),
726            self.runner_from_self_checker(expose),
727            self.resolver_from_self_checker(expose),
728            self.dictionary_from_self_checker(expose),
729            self.config_from_self_checker(expose),
730        ] {
731            checker.validate("exposed")?;
732        }
733
734        // Ensure directory rights are valid.
735        if let Some(_) = expose.directory.as_ref() {
736            if expose.from.iter().any(|r| *r == ExposeFromRef::Self_) || expose.rights.is_some() {
737                if let Some(rights) = expose.rights.as_ref() {
738                    self.validate_directory_rights(&rights)?;
739                }
740            }
741
742            // Exposing a subdirectory makes sense for routing but when exposing to framework,
743            // the subdir should be exposed directly.
744            if expose.to == Some(ExposeToRef::Framework) {
745                if expose.subdir.is_some() {
746                    return Err(Error::validate(
747                        "`subdir` is not supported for expose to framework. Directly expose the subdirectory instead."
748                    ));
749                }
750            }
751        }
752
753        if let Some(event_stream) = &expose.event_stream {
754            if event_stream.iter().len() > 1 && expose.r#as.is_some() {
755                return Err(Error::validate(format!(
756                    "as cannot be used with multiple event streams"
757                )));
758            }
759            if let Some(ExposeToRef::Framework) = &expose.to {
760                return Err(Error::validate(format!("cannot expose an event_stream to framework")));
761            }
762            for from in expose.from.iter() {
763                if from == &ExposeFromRef::Self_ {
764                    return Err(Error::validate(format!("Cannot expose event_streams from self")));
765                }
766            }
767            if let Some(scopes) = &expose.scope {
768                for scope in scopes {
769                    match scope {
770                        EventScope::Named(name) => {
771                            if !self.all_children.contains_key(name)
772                                && !self.all_collections.contains(name)
773                            {
774                                return Err(Error::validate(format!("event_stream scope {} did not match a component or collection in this .cml file.", name.as_str())));
775                            }
776                        }
777                    }
778                }
779            }
780        }
781
782        for ref_ in expose.from.iter() {
783            if let ExposeFromRef::Dictionary(d) = ref_ {
784                if expose.event_stream.is_some() {
785                    return Err(Error::validate(
786                        "Dictionaries do not support \"event_stream\" capabilities",
787                    ));
788                }
789                match &d.root {
790                    RootDictionaryRef::Self_ | RootDictionaryRef::Named(_) => {}
791                    RootDictionaryRef::Parent => {
792                        return Err(Error::validate(
793                            "`expose` dictionary path must begin with `self` or `#<child-name>`",
794                        ));
795                    }
796                }
797            }
798        }
799
800        // Ensure we haven't already exposed an entity of the same name.
801        let capability_ids = CapabilityId::from_offer_expose(expose)?;
802        for capability_id in capability_ids {
803            let mut ids = &mut *used_ids;
804            if expose.to == Some(ExposeToRef::Framework) {
805                ids = &mut *exposed_to_framework_ids;
806            }
807            if ids.insert(capability_id.to_string(), capability_id.clone()).is_some() {
808                if let CapabilityId::Service(_) = capability_id {
809                    // Services may have duplicates (aggregation).
810                } else {
811                    return Err(Error::validate(format!(
812                        "\"{}\" is a duplicate \"expose\" target capability for \"{}\"",
813                        capability_id,
814                        expose.to.as_ref().unwrap_or(&ExposeToRef::Parent)
815                    )));
816                }
817            }
818        }
819
820        // Validate `from` (done last because this validation depends on the capability type, which
821        // must be validated first)
822        self.validate_from_clause(
823            "expose",
824            expose,
825            &expose.source_availability,
826            &expose.availability,
827        )?;
828
829        Ok(())
830    }
831
832    fn validate_offer(
833        &mut self,
834        offer: &'a Offer,
835        used_ids: &mut HashMap<Name, HashMap<String, CapabilityId<'a>>>,
836        protocols_offered_to_all: &[&'a Offer],
837    ) -> Result<(), Error> {
838        offer.capability_type()?;
839        for checker in [
840            self.service_from_self_checker(offer),
841            self.protocol_from_self_checker(offer),
842            self.directory_from_self_checker(offer),
843            self.storage_from_self_checker(offer),
844            self.runner_from_self_checker(offer),
845            self.resolver_from_self_checker(offer),
846            self.dictionary_from_self_checker(offer),
847            self.config_from_self_checker(offer),
848        ] {
849            checker.validate("offered")?;
850        }
851
852        if let Some(stream) = offer.event_stream.as_ref() {
853            if stream.iter().len() > 1 && offer.r#as.is_some() {
854                return Err(Error::validate(format!("as cannot be used with multiple events")));
855            }
856            for from in &offer.from {
857                match from {
858                    OfferFromRef::Self_ => {
859                        return Err(Error::validate(format!(
860                            "cannot offer an event_stream from self"
861                        )));
862                    }
863                    _ => {}
864                }
865            }
866        }
867
868        // Ensure directory rights are valid.
869        if let Some(_) = offer.directory.as_ref() {
870            if offer.from.iter().any(|r| *r == OfferFromRef::Self_) || offer.rights.is_some() {
871                if let Some(rights) = offer.rights.as_ref() {
872                    self.validate_directory_rights(&rights)?;
873                }
874            }
875        }
876
877        if let Some(storage) = offer.storage.as_ref() {
878            for storage in storage {
879                if offer.from.iter().any(|r| r.is_named()) {
880                    return Err(Error::validate(format!(
881                    "Storage \"{}\" is offered from a child, but storage capabilities cannot be exposed", storage)));
882                }
883            }
884        }
885
886        for ref_ in offer.from.iter() {
887            if let OfferFromRef::Dictionary(d) = ref_ {
888                match &d.root {
889                    RootDictionaryRef::Self_
890                    | RootDictionaryRef::Named(_)
891                    | RootDictionaryRef::Parent => {}
892                }
893
894                if offer.storage.is_some() {
895                    return Err(Error::validate(
896                        "Dictionaries do not support \"storage\" capabilities",
897                    ));
898                }
899                if offer.event_stream.is_some() {
900                    return Err(Error::validate(
901                        "Dictionaries do not support \"event_stream\" capabilities",
902                    ));
903                }
904            }
905        }
906
907        // Ensure that dependency is set for the right capabilities.
908        if !offer_can_have_dependency(offer) && offer.dependency.is_some() {
909            return Err(Error::validate(
910                "Dependency can only be provided for protocol, directory, and service capabilities",
911            ));
912        }
913
914        // Validate every target of this offer.
915        let target_cap_ids = CapabilityId::from_offer_expose(offer)?;
916        for to in &offer.to {
917            // Ensure the "to" value is a child, collection, or dictionary capability.
918            let to_target = match to {
919                OfferToRef::All => continue,
920                OfferToRef::Named(ref to_target) => {
921                    // Verify that only a legal set of offers-to-all are made, including that any
922                    // offer to all duplicated as an offer to a specific component are exactly the same
923                    for offer_to_all in protocols_offered_to_all {
924                        offer_to_all_would_duplicate(offer_to_all, offer, to_target)?;
925                    }
926
927                    // Check that any referenced child actually exists.
928                    if self.all_children.contains_key(to_target)
929                        || self.all_collections.contains(to_target)
930                    {
931                        // Allowed.
932                    } else {
933                        if let OneOrMany::One(from) = &offer.from {
934                            return Err(Error::validate(format!(
935                                "\"{to}\" is an \"offer\" target from \"{from}\" but it does \
936                                not appear in \"children\" or \"collections\"",
937                            )));
938                        } else {
939                            return Err(Error::validate(format!(
940                                "\"{to}\" is an \"offer\" target but it does not appear in \
941                                \"children\" or \"collections\"",
942                            )));
943                        }
944                    }
945
946                    // Ensure we are not offering a capability back to its source.
947                    if let Some(storage) = offer.storage.as_ref() {
948                        for storage in storage {
949                            // Storage can only have a single `from` clause and this has been
950                            // verified.
951                            if let OneOrMany::One(OfferFromRef::Self_) = &offer.from {
952                                if let Some(CapabilityFromRef::Named(source)) =
953                                    self.all_storages.get(storage)
954                                {
955                                    if to_target == source {
956                                        return Err(Error::validate(format!(
957                                            "Storage offer target \"{}\" is same as source",
958                                            to
959                                        )));
960                                    }
961                                }
962                            }
963                        }
964                    } else {
965                        for reference in offer.from.iter() {
966                            // Weak offers from a child to itself are acceptable.
967                            if offer_dependency(offer) == DependencyType::Weak {
968                                continue;
969                            }
970                            match reference {
971                                OfferFromRef::Named(name) if name == to_target => {
972                                    return Err(Error::validate(format!(
973                                        "Offer target \"{}\" is same as source",
974                                        to
975                                    )));
976                                }
977                                _ => {}
978                            }
979                        }
980                    }
981                    to_target
982                }
983                OfferToRef::OwnDictionary(ref to_target) => {
984                    if let Ok(capability_ids) = CapabilityId::from_offer_expose(offer) {
985                        for id in capability_ids {
986                            match &id {
987                                CapabilityId::Protocol(_)
988                                | CapabilityId::Dictionary(_)
989                                | CapabilityId::Runner(_)
990                                | CapabilityId::Resolver(_)
991                                | CapabilityId::Configuration(_) => {}
992                                CapabilityId::Directory(_)
993                                | CapabilityId::Service(_)
994                                | CapabilityId::Storage(_)
995                                | CapabilityId::EventStream(_) => {
996                                    let type_name = id.type_str();
997                                    return Err(Error::validate(format!(
998                                        "\"offer\" to dictionary \"{to}\" for \"{type_name}\" but \
999                                        dictionaries do not support this type yet."
1000                                    )));
1001                                }
1002                                CapabilityId::UsedService(_)
1003                                | CapabilityId::UsedProtocol(_)
1004                                | CapabilityId::UsedDirectory(_)
1005                                | CapabilityId::UsedStorage(_)
1006                                | CapabilityId::UsedEventStream(_)
1007                                | CapabilityId::UsedRunner(_)
1008                                | CapabilityId::UsedConfiguration(_) => {
1009                                    unreachable!("this is not a use")
1010                                }
1011                            }
1012                        }
1013                    }
1014                    // Check that any referenced child actually exists.
1015                    let Some(d) = self.all_dictionaries.get(&to_target) else {
1016                        return Err(Error::validate(format!(
1017                            "\"offer\" has dictionary target \"{to}\" but \"{to_target}\" \
1018                                is not a dictionary capability defined by this component"
1019                        )));
1020                    };
1021                    if d.path.is_some() {
1022                        return Err(Error::validate(format!(
1023                            "\"offer\" has dictionary target \"{to}\" but \"{to_target}\" \
1024                            sets \"path\". Therefore, it is a dynamic dictionary that \
1025                            does not allow offers into it."
1026                        )));
1027                    }
1028                    to_target
1029                }
1030            };
1031
1032            // Ensure that a target is not offered more than once.
1033            let ids_for_entity = used_ids.entry(to_target.clone()).or_insert(HashMap::new());
1034            for target_cap_id in &target_cap_ids {
1035                if ids_for_entity.insert(target_cap_id.to_string(), target_cap_id.clone()).is_some()
1036                {
1037                    if let CapabilityId::Service(_) = target_cap_id {
1038                        // Services may have duplicates (aggregation).
1039                    } else {
1040                        return Err(Error::validate(format!(
1041                            "\"{}\" is a duplicate \"offer\" target capability for \"{}\"",
1042                            target_cap_id, to
1043                        )));
1044                    }
1045                }
1046            }
1047
1048            // Collect strong dependencies. We'll check for dependency cycles after all offer
1049            // declarations are validated.
1050            for from in offer.from.iter() {
1051                if offer_dependency(offer) == DependencyType::Strong {
1052                    for source in self.expand_source_dependencies(offer.names(), &from.into()) {
1053                        let target = DependencyNode::offer_to_ref(to);
1054                        self.add_strong_dep(source, target);
1055                    }
1056                }
1057            }
1058        }
1059
1060        // Validate `from` (done last because this validation depends on the capability type, which
1061        // must be validated first)
1062        self.validate_from_clause("offer", offer, &offer.source_availability, &offer.availability)?;
1063
1064        Ok(())
1065    }
1066
1067    fn validate_required_offer_decls(&self) -> Result<(), Error> {
1068        let children_stub = Vec::new();
1069        let children = self.document.children.as_ref().unwrap_or(&children_stub);
1070        let collections_stub = Vec::new();
1071        let collections = self.document.collections.as_ref().unwrap_or(&collections_stub);
1072        let offers_stub = Vec::new();
1073        let offers = self.document.offer.as_ref().unwrap_or(&offers_stub);
1074
1075        for required_offer in self.capability_requirements.must_offer {
1076            // for each child, check if any offer is:
1077            //   1) Targeting this child (or all)
1078            //   AND
1079            //   2) Offering the current required capability
1080            for child in children.iter() {
1081                if !offers
1082                    .iter()
1083                    .any(|offer| Self::has_required_offer(offer, &child.name, required_offer))
1084                {
1085                    let capability_type = required_offer.offer_type();
1086                    return Err(Error::validate(format!(
1087                        r#"{capability_type} "{}" is not offered to child component "{}" but it is a required offer"#,
1088                        required_offer.name(),
1089                        child.name
1090                    )));
1091                }
1092            }
1093
1094            for collection in collections.iter() {
1095                if !offers
1096                    .iter()
1097                    .any(|offer| Self::has_required_offer(offer, &collection.name, required_offer))
1098                {
1099                    let capability_type = required_offer.offer_type();
1100                    return Err(Error::validate(format!(
1101                        r#"{capability_type} "{}" is not offered to collection "{}" but it is a required offer"#,
1102                        required_offer.name(),
1103                        collection.name
1104                    )));
1105                }
1106            }
1107        }
1108
1109        Ok(())
1110    }
1111
1112    fn has_required_offer(
1113        offer: &Offer,
1114        target_name: &Name,
1115        required_offer: &OfferToAllCapability<'_>,
1116    ) -> bool {
1117        let names_this_collection = offer.to.iter().any(|target| match target {
1118            OfferToRef::Named(ref name) => name == target_name,
1119            OfferToRef::All => true,
1120            OfferToRef::OwnDictionary(_) => false,
1121        });
1122        let capability_names = match required_offer {
1123            OfferToAllCapability::Dictionary(_) => offer.dictionary.as_ref(),
1124            OfferToAllCapability::Protocol(_) => offer.protocol.as_ref(),
1125        };
1126        let names_this_capability = match capability_names.as_ref() {
1127            Some(OneOrMany::Many(names)) => {
1128                names.iter().any(|cap_name| cap_name.as_str() == required_offer.name())
1129            }
1130            Some(OneOrMany::One(name)) => {
1131                let cap_name = offer.r#as.as_ref().unwrap_or(name);
1132                cap_name.as_str() == required_offer.name()
1133            }
1134            None => false,
1135        };
1136        names_this_collection && names_this_capability
1137    }
1138
1139    fn validate_required_use_decls(&self) -> Result<(), Error> {
1140        let use_decls_stub = Vec::new();
1141        let use_decls = self.document.r#use.as_ref().unwrap_or(&use_decls_stub);
1142
1143        for required_usage in self.capability_requirements.must_use {
1144            if !use_decls.iter().any(|usage| match usage.protocol.as_ref() {
1145                None => false,
1146                Some(protocol) => protocol
1147                    .iter()
1148                    .any(|protocol_name| protocol_name.as_str() == required_usage.name()),
1149            }) {
1150                return Err(Error::validate(format!(
1151                    r#"Protocol "{}" is not used by a component but is required by all"#,
1152                    required_usage.name(),
1153                )));
1154            }
1155        }
1156
1157        Ok(())
1158    }
1159
1160    fn expand_source_dependencies(
1161        &self,
1162        names: Vec<&'a Name>,
1163        source: &AnyRef<'a>,
1164    ) -> Vec<DependencyNode<'a>> {
1165        let mut sources = vec![];
1166        match source {
1167            AnyRef::Self_ => {
1168                let mut has_named = false;
1169                for name in names {
1170                    if self.all_dictionaries.contains_key(name)
1171                        || self.all_storages.contains_key(name)
1172                    {
1173                        sources.push(DependencyNode::Named(name));
1174                        has_named = true;
1175                    }
1176                }
1177                if !has_named {
1178                    sources.push(DependencyNode::Self_);
1179                }
1180            }
1181            AnyRef::Dictionary(d) => {
1182                let mut has_named = false;
1183                match d.root {
1184                    RootDictionaryRef::Self_ => {
1185                        let dictionary_name = d.path.iter_segments().next().unwrap();
1186                        if self.all_dictionaries.contains_key(dictionary_name) {
1187                            // This should be true, if the cml didn't contain a syntax error that
1188                            // omitted the definition for `dictionary_name`.
1189                            sources.push(DependencyNode::Named(dictionary_name));
1190                            has_named = true;
1191                        }
1192                    }
1193                    _ => {}
1194                }
1195                if !has_named {
1196                    if let Some(s) = DependencyNode::from_dictionary_ref(d) {
1197                        sources.push(s);
1198                    }
1199                }
1200            }
1201            AnyRef::Named(n) => {
1202                sources.push(DependencyNode::Named(n));
1203            }
1204            AnyRef::Parent | AnyRef::Void | AnyRef::Framework | AnyRef::Debug => {}
1205            AnyRef::OwnDictionary(_) => unreachable!("can't be a source"),
1206        }
1207        sources
1208    }
1209
1210    /// Adds a strong dependency between two nodes in the dependency graph between `source` and
1211    /// `target`.
1212    ///
1213    /// `name` is the name of the capability being routed (if applicable).
1214    fn add_strong_dep(&mut self, source: DependencyNode<'a>, target: DependencyNode<'a>) {
1215        match (source, target) {
1216            (DependencyNode::Self_, DependencyNode::Self_) => {
1217                // `self` dependencies (e.g. `use from self`) are allowed.
1218            }
1219            (source, target) => {
1220                self.strong_dependencies.add_edge(source, target);
1221            }
1222        }
1223    }
1224
1225    /// Validates that the from clause:
1226    ///
1227    /// - is applicable to the capability type,
1228    /// - does not contain duplicates,
1229    /// - references names that exist.
1230    /// - has availability "optional" if the source is "void"
1231    ///
1232    /// `verb` is used in any error messages and is expected to be "offer", "expose", etc.
1233    fn validate_from_clause<T>(
1234        &self,
1235        verb: &str,
1236        cap: &T,
1237        source_availability: &Option<SourceAvailability>,
1238        availability: &Option<Availability>,
1239    ) -> Result<(), Error>
1240    where
1241        T: CapabilityClause + FromClause,
1242    {
1243        let from = cap.from_();
1244        if cap.service().is_none() && from.is_many() {
1245            return Err(Error::validate(format!(
1246                "\"{}\" capabilities cannot have multiple \"from\" clauses",
1247                cap.capability_type().unwrap()
1248            )));
1249        }
1250
1251        if from.is_many() {
1252            ensure_no_duplicate_values(&cap.from_())?;
1253        }
1254
1255        let reference_description = format!("\"{}\" source", verb);
1256        for from_clause in from {
1257            // If this is a protocol, it could reference either a child or a storage capability
1258            // (for the storage admin protocol).
1259            let ref_validity_res = if cap.protocol().is_some() {
1260                self.validate_component_child_or_capability_ref(
1261                    &reference_description,
1262                    &from_clause,
1263                )
1264            } else if cap.service().is_some() {
1265                // Services can also be sourced from collections.
1266                self.validate_component_child_or_collection_ref(
1267                    &reference_description,
1268                    &from_clause,
1269                )
1270            } else {
1271                self.validate_component_child_ref(&reference_description, &from_clause)
1272            };
1273
1274            match ref_validity_res {
1275                Ok(()) if from_clause == AnyRef::Void => {
1276                    // The source is valid and void
1277                    if availability != &Some(Availability::Optional) {
1278                        return Err(Error::validate(format!(
1279                            "capabilities with a source of \"void\" must have an availability of \"optional\", capabilities: \"{}\", from: \"{}\"",
1280                            cap.names().iter().map(|n| n.as_str()).collect::<Vec<_>>().join(", "),
1281                            cap.from_(),
1282                        )));
1283                    }
1284                }
1285                Ok(()) => {
1286                    // The source is valid and not void.
1287                }
1288                Err(_) if source_availability == &Some(SourceAvailability::Unknown) => {
1289                    // The source is invalid, and will be rewritten to void
1290                    if availability != &Some(Availability::Optional) && availability != &None {
1291                        return Err(Error::validate(format!(
1292                            "capabilities with an intentionally missing source must have an availability that is either unset or \"optional\", capabilities: \"{}\", from: \"{}\"",
1293                            cap.names().iter().map(|n| n.as_str()).collect::<Vec<_>>().join(", "),
1294                            cap.from_(),
1295                        )));
1296                    }
1297                }
1298                Err(e) => {
1299                    // The source is invalid, but we're expecting it to be valid.
1300                    return Err(e);
1301                }
1302            }
1303        }
1304        Ok(())
1305    }
1306
1307    /// Validates that the given component exists.
1308    ///
1309    /// - `reference_description` is a human-readable description of the reference used in error
1310    ///   message, such as `"offer" source`.
1311    /// - `component_ref` is a reference to a component. If the reference is a named child, we
1312    ///   ensure that the child component exists.
1313    fn validate_component_child_ref(
1314        &self,
1315        reference_description: &str,
1316        component_ref: &AnyRef<'_>,
1317    ) -> Result<(), Error> {
1318        match component_ref {
1319            AnyRef::Named(name) => {
1320                // Ensure we have a child defined by that name.
1321                if !self.all_children.contains_key(name) {
1322                    return Err(Error::validate(format!(
1323                        "{} \"{}\" does not appear in \"children\"",
1324                        reference_description, component_ref
1325                    )));
1326                }
1327                Ok(())
1328            }
1329            // We don't attempt to validate other reference types.
1330            _ => Ok(()),
1331        }
1332    }
1333
1334    /// Validates that the given component/collection exists.
1335    ///
1336    /// - `reference_description` is a human-readable description of the reference used in error
1337    ///   message, such as `"offer" source`.
1338    /// - `component_ref` is a reference to a component/collection. If the reference is a named
1339    ///   child or collection, we ensure that the child component/collection exists.
1340    fn validate_component_child_or_collection_ref(
1341        &self,
1342        reference_description: &str,
1343        component_ref: &AnyRef<'_>,
1344    ) -> Result<(), Error> {
1345        match component_ref {
1346            AnyRef::Named(name) => {
1347                // Ensure we have a child or collection defined by that name.
1348                if !self.all_children.contains_key(name) && !self.all_collections.contains(name) {
1349                    return Err(Error::validate(format!(
1350                        "{} \"{}\" does not appear in \"children\" or \"collections\"",
1351                        reference_description, component_ref
1352                    )));
1353                }
1354                Ok(())
1355            }
1356            // We don't attempt to validate other reference types.
1357            _ => Ok(()),
1358        }
1359    }
1360
1361    /// Validates that the given capability exists.
1362    ///
1363    /// - `reference_description` is a human-readable description of the reference used in error
1364    ///   message, such as `"offer" source`.
1365    /// - `capability_ref` is a reference to a capability. If the reference is a named capability,
1366    ///   we ensure that the capability exists.
1367    fn validate_component_capability_ref(
1368        &self,
1369        reference_description: &str,
1370        capability_ref: &AnyRef<'_>,
1371    ) -> Result<(), Error> {
1372        match capability_ref {
1373            AnyRef::Named(name) => {
1374                if !self.all_capability_names.contains(name) {
1375                    return Err(Error::validate(format!(
1376                        "{} \"{}\" does not appear in \"capabilities\"",
1377                        reference_description, capability_ref
1378                    )));
1379                }
1380                Ok(())
1381            }
1382            _ => Ok(()),
1383        }
1384    }
1385
1386    /// Validates that the given child component, collection, or capability exists.
1387    ///
1388    /// - `reference_description` is a human-readable description of the reference used in error
1389    ///   message, such as `"offer" source`.
1390    /// - `ref_` is a reference to a child component or capability. If the reference contains a
1391    ///   name, we ensure that a child component or a capability with the name exists.
1392    fn validate_component_child_or_capability_ref(
1393        &self,
1394        reference_description: &str,
1395        ref_: &AnyRef<'_>,
1396    ) -> Result<(), Error> {
1397        if self.validate_component_child_ref(reference_description, ref_).is_err()
1398            && self.validate_component_capability_ref(reference_description, ref_).is_err()
1399        {
1400            return Err(Error::validate(format!(
1401                "{} \"{}\" does not appear in \"children\" or \"capabilities\"",
1402                reference_description, ref_
1403            )));
1404        }
1405        Ok(())
1406    }
1407
1408    /// Validates that directory rights for all route types are valid, i.e that it does not
1409    /// contain duplicate rights.
1410    fn validate_directory_rights(&self, rights_clause: &Rights) -> Result<(), Error> {
1411        let mut rights = HashSet::new();
1412        for right_token in rights_clause.0.iter() {
1413            for right in right_token.expand() {
1414                if !rights.insert(right) {
1415                    return Err(Error::validate(format!(
1416                        "\"{}\" is duplicated in the rights clause.",
1417                        right_token
1418                    )));
1419                }
1420            }
1421        }
1422        Ok(())
1423    }
1424
1425    /// Ensure we don't have a component with a "program" block which fails to specify a runner.
1426    /// This should only be called if the manifest doesn't "use" a runner.
1427    fn validate_runner_specified(&self, program: Option<&Program>) -> Result<(), Error> {
1428        match program {
1429            Some(program) => match program.runner {
1430                Some(_) => Ok(()),
1431                None => {
1432                    return Err(Error::validate(
1433                        "Component has a `program` block defined, but doesn't specify a `runner`. \
1434                        Components need to use a runner to actually execute code.",
1435                    ));
1436                }
1437            },
1438            None => Ok(()),
1439        }
1440    }
1441
1442    /// Ensure we don't have a component with a "program" block which fails to specify a runner.
1443    /// This should only be called if the manifest "use"s a runner.
1444    fn validate_runner_not_specified(&self, program: Option<&Program>) -> Result<(), Error> {
1445        match program {
1446            Some(program) => match program.runner {
1447                Some(_) => {
1448                    // Use/runner always conflicts with program/runner, because use/runner
1449                    // can't be from environment in CML.
1450                    return Err(Error::validate(
1451                        "Component has conflicting runners in `program` block and `use` block.",
1452                    ));
1453                }
1454                None => Ok(()),
1455            },
1456            None => Ok(()),
1457        }
1458    }
1459
1460    fn validate_config(
1461        &self,
1462        fields: &Option<BTreeMap<ConfigKey, ConfigValueType>>,
1463    ) -> Result<(), Error> {
1464        // If we `use` a config capability optionally without a default then it has to exist in the `config` block.
1465        // Collect the names of the keys here.
1466        let optional_use_keys: BTreeMap<ConfigKey, ConfigValueType> = self
1467            .document
1468            .r#use
1469            .iter()
1470            .flatten()
1471            .map(|u| {
1472                if u.config == None {
1473                    return None;
1474                }
1475                if u.availability == Some(Availability::Required) || u.availability == None {
1476                    return None;
1477                }
1478                if let Some(_) = u.config_default.as_ref() {
1479                    return None;
1480                }
1481                let key = ConfigKey(u.key.clone().expect("key should be set").into());
1482                let value = use_config_to_value_type(u).expect("config type should be valid");
1483                Some((key, value))
1484            })
1485            .flatten()
1486            .collect();
1487
1488        let Some(fields) = fields else {
1489            if !optional_use_keys.is_empty() {
1490                return Err(Error::validate(
1491                    "Optionally using a config capability without a default requires a matching 'config' section.",
1492                ));
1493            }
1494            return Ok(());
1495        };
1496
1497        if fields.is_empty() {
1498            return Err(Error::validate("'config' section is empty"));
1499        }
1500
1501        for (key, value) in optional_use_keys {
1502            if !fields.contains_key(&key) {
1503                return Err(Error::validate(format!(
1504                    "'config' section must contain key for optional use '{}'",
1505                    key
1506                )));
1507            }
1508            if fields.get(&key) != Some(&value) {
1509                return Err(Error::validate(format!(
1510                    "Use and config block differ on type for key '{}'",
1511                    key
1512                )));
1513            }
1514        }
1515
1516        Ok(())
1517    }
1518
1519    fn validate_environment(&mut self, environment: &'a Environment) -> Result<(), Error> {
1520        match &environment.extends {
1521            Some(EnvironmentExtends::None) => {
1522                if environment.stop_timeout_ms.is_none() {
1523                    return Err(Error::validate(
1524                        "'__stop_timeout_ms' must be provided if the environment does not extend \
1525                        another environment",
1526                    ));
1527                }
1528            }
1529            Some(EnvironmentExtends::Realm) | None => {}
1530        }
1531
1532        if let Some(runners) = &environment.runners {
1533            let mut used_names = HashMap::new();
1534            for registration in runners {
1535                // Validate that this name is not already used.
1536                let name = registration.r#as.as_ref().unwrap_or(&registration.runner);
1537                if let Some(previous_runner) = used_names.insert(name, &registration.runner) {
1538                    return Err(Error::validate(format!(
1539                        "Duplicate runners registered under name \"{}\": \"{}\" and \"{}\".",
1540                        name, &registration.runner, previous_runner
1541                    )));
1542                }
1543
1544                // Ensure that the environment is defined in `runners` if it comes from `self`.
1545                if registration.from == RegistrationRef::Self_
1546                    && !self.all_runners.contains(&registration.runner)
1547                {
1548                    return Err(Error::validate(format!(
1549                        "Runner \"{}\" registered in environment is not in \"runners\"",
1550                        &registration.runner,
1551                    )));
1552                }
1553
1554                self.validate_component_child_ref(
1555                    &format!("\"{}\" runner source", &registration.runner),
1556                    &AnyRef::from(&registration.from),
1557                )?;
1558
1559                // Ensure there are no cycles, such as a resolver in an environment being assigned
1560                // to a child which the resolver depends on.
1561                if let Some(source) = DependencyNode::registration_ref(&registration.from) {
1562                    let target = DependencyNode::Named(&environment.name);
1563                    self.add_strong_dep(source, target);
1564                }
1565            }
1566        }
1567
1568        if let Some(resolvers) = &environment.resolvers {
1569            let mut used_schemes = HashMap::new();
1570            for registration in resolvers {
1571                // Validate that the scheme is not already used.
1572                if let Some(previous_resolver) =
1573                    used_schemes.insert(&registration.scheme, &registration.resolver)
1574                {
1575                    return Err(Error::validate(format!(
1576                        "scheme \"{}\" for resolver \"{}\" is already registered; \
1577                        previously registered to resolver \"{}\".",
1578                        &registration.scheme, &registration.resolver, previous_resolver
1579                    )));
1580                }
1581
1582                self.validate_component_child_ref(
1583                    &format!("\"{}\" resolver source", &registration.resolver),
1584                    &AnyRef::from(&registration.from),
1585                )?;
1586                // Ensure there are no cycles, such as a resolver in an environment being assigned
1587                // to a child which the resolver depends on.
1588                if let Some(source) = DependencyNode::registration_ref(&registration.from) {
1589                    let target = DependencyNode::Named(&environment.name);
1590                    self.add_strong_dep(source, target);
1591                }
1592            }
1593        }
1594
1595        if let Some(debug_capabilities) = &environment.debug {
1596            for debug in debug_capabilities {
1597                self.protocol_from_self_checker(debug).validate("registered as debug")?;
1598                self.validate_from_clause("debug", debug, &None, &None)?;
1599                // Ensure there are no cycles, such as a debug capability in an environment being
1600                // assigned to the child which is providing the capability.
1601                for source in self.expand_source_dependencies(debug.names(), &(&debug.from).into())
1602                {
1603                    let target = DependencyNode::Named(&environment.name);
1604                    self.add_strong_dep(source, target);
1605                }
1606            }
1607        }
1608        Ok(())
1609    }
1610
1611    fn service_from_self_checker<'b>(
1612        &'b self,
1613        input: &'b (impl CapabilityClause + FromClause),
1614    ) -> RouteFromSelfChecker<'b> {
1615        RouteFromSelfChecker {
1616            capability_name: input.service(),
1617            from: input.from_(),
1618            container: &self.all_services,
1619            all_dictionaries: &self.all_dictionaries,
1620            typename: "service",
1621        }
1622    }
1623
1624    fn protocol_from_self_checker<'b>(
1625        &'b self,
1626        input: &'b (impl CapabilityClause + FromClause),
1627    ) -> RouteFromSelfChecker<'b> {
1628        RouteFromSelfChecker {
1629            capability_name: input.protocol(),
1630            from: input.from_(),
1631            container: &self.all_protocols,
1632            all_dictionaries: &self.all_dictionaries,
1633            typename: "protocol",
1634        }
1635    }
1636
1637    fn directory_from_self_checker<'b>(
1638        &'b self,
1639        input: &'b (impl CapabilityClause + FromClause),
1640    ) -> RouteFromSelfChecker<'b> {
1641        RouteFromSelfChecker {
1642            capability_name: input.directory(),
1643            from: input.from_(),
1644            container: &self.all_directories,
1645            all_dictionaries: &self.all_dictionaries,
1646            typename: "directory",
1647        }
1648    }
1649
1650    fn storage_from_self_checker<'b>(
1651        &'b self,
1652        input: &'b (impl CapabilityClause + FromClause),
1653    ) -> RouteFromSelfChecker<'b> {
1654        RouteFromSelfChecker {
1655            capability_name: input.storage(),
1656            from: input.from_(),
1657            container: &self.all_storages,
1658            all_dictionaries: &self.all_dictionaries,
1659            typename: "storage",
1660        }
1661    }
1662
1663    fn runner_from_self_checker<'b>(
1664        &'b self,
1665        input: &'b (impl CapabilityClause + FromClause),
1666    ) -> RouteFromSelfChecker<'b> {
1667        RouteFromSelfChecker {
1668            capability_name: input.runner(),
1669            from: input.from_(),
1670            container: &self.all_runners,
1671            all_dictionaries: &self.all_dictionaries,
1672            typename: "runner",
1673        }
1674    }
1675
1676    fn resolver_from_self_checker<'b>(
1677        &'b self,
1678        input: &'b (impl CapabilityClause + FromClause),
1679    ) -> RouteFromSelfChecker<'b> {
1680        RouteFromSelfChecker {
1681            capability_name: input.resolver(),
1682            from: input.from_(),
1683            container: &self.all_resolvers,
1684            all_dictionaries: &self.all_dictionaries,
1685            typename: "resolver",
1686        }
1687    }
1688
1689    fn dictionary_from_self_checker<'b>(
1690        &'b self,
1691        input: &'b (impl CapabilityClause + FromClause),
1692    ) -> RouteFromSelfChecker<'b> {
1693        RouteFromSelfChecker {
1694            capability_name: input.dictionary(),
1695            from: input.from_(),
1696            container: &self.all_dictionaries,
1697            all_dictionaries: &self.all_dictionaries,
1698            typename: "dictionary",
1699        }
1700    }
1701
1702    fn config_from_self_checker<'b>(
1703        &'b self,
1704        input: &'b (impl CapabilityClause + FromClause),
1705    ) -> RouteFromSelfChecker<'b> {
1706        RouteFromSelfChecker {
1707            capability_name: input.config(),
1708            from: input.from_(),
1709            container: &self.all_configs,
1710            all_dictionaries: &self.all_dictionaries,
1711            typename: "config",
1712        }
1713    }
1714}
1715
1716/// Helper type that assists with validating declarations of `{use, offer, expose} from self`.
1717struct RouteFromSelfChecker<'a> {
1718    /// The value of the capability property (protocol, service, etc.)
1719    capability_name: Option<OneOrMany<&'a Name>>,
1720
1721    /// The value of `from`.
1722    from: OneOrMany<AnyRef<'a>>,
1723
1724    /// A [Container] which is used to check for the existence of a capability definition.
1725    container: &'a dyn Container,
1726
1727    /// Reference to [ValidationContext::all_dictionaries].
1728    all_dictionaries: &'a HashMap<&'a Name, &'a Capability>,
1729
1730    /// The string name for the capability's type.
1731    typename: &'static str,
1732}
1733
1734impl<'a> RouteFromSelfChecker<'a> {
1735    fn validate(self, operand: &'static str) -> Result<(), Error> {
1736        let Self { capability_name, from, container, all_dictionaries, typename } = self;
1737        let Some(capability) = capability_name else {
1738            return Ok(());
1739        };
1740        for capability in capability {
1741            for from in &from {
1742                match from {
1743                    AnyRef::Self_ if !container.contains(capability) => {
1744                        return Err(Error::validate(format!(
1745                            "{typename} \"{capability}\" is {operand} from self, so it \
1746                            must be declared as a \"{typename}\" in \"capabilities\"",
1747                        )));
1748                    }
1749                    AnyRef::Dictionary(DictionaryRef { root: RootDictionaryRef::Self_, path }) => {
1750                        let first_segment = path.iter_segments().next().unwrap();
1751                        if !all_dictionaries.contains_key(first_segment) {
1752                            return Err(Error::validate(format!(
1753                                "{typename} \"{capability}\" is {operand} from \"self/{path}\", so \
1754                                \"{first_segment}\" must be declared as a \"dictionary\" in \"capabilities\"",
1755                            )));
1756                        }
1757                    }
1758                    _ => {}
1759                }
1760            }
1761        }
1762        Ok(())
1763    }
1764}
1765
1766/// [Container] provides a capability type agnostic trait to check for the existence of a
1767/// capability definition of a particular type. This is useful for writing common validation
1768/// functions.
1769trait Container {
1770    fn contains(&self, key: &Name) -> bool;
1771}
1772
1773impl<'a> Container for HashSet<&'a Name> {
1774    fn contains(&self, key: &Name) -> bool {
1775        self.contains(key)
1776    }
1777}
1778
1779impl<'a, T> Container for HashMap<&'a Name, T> {
1780    fn contains(&self, key: &Name) -> bool {
1781        self.contains_key(key)
1782    }
1783}
1784
1785// Construct the config type information out of a `use` for a configuration capability.
1786// This will return validation errors if the `use` is missing fields.
1787pub fn use_config_to_value_type(u: &Use) -> Result<ConfigValueType, Error> {
1788    let config = u.config.clone().expect("Only call use_config_to_value_type on a Config");
1789
1790    let Some(config_type) = u.config_type.as_ref() else {
1791        return Err(Error::validate(format!("Config '{}' is missing field 'type'", config)));
1792    };
1793
1794    let config_type = match config_type {
1795        ConfigType::Bool => ConfigValueType::Bool { mutability: None },
1796        ConfigType::Uint8 => ConfigValueType::Uint8 { mutability: None },
1797        ConfigType::Uint16 => ConfigValueType::Uint16 { mutability: None },
1798        ConfigType::Uint32 => ConfigValueType::Uint32 { mutability: None },
1799        ConfigType::Uint64 => ConfigValueType::Uint64 { mutability: None },
1800        ConfigType::Int8 => ConfigValueType::Int8 { mutability: None },
1801        ConfigType::Int16 => ConfigValueType::Int16 { mutability: None },
1802        ConfigType::Int32 => ConfigValueType::Int32 { mutability: None },
1803        ConfigType::Int64 => ConfigValueType::Int64 { mutability: None },
1804        ConfigType::String => {
1805            let Some(max_size) = u.config_max_size else {
1806                return Err(Error::validate(format!(
1807                    "Config '{}' is type String but is missing field 'max_size'",
1808                    config
1809                )));
1810            };
1811            ConfigValueType::String { max_size: max_size.into(), mutability: None }
1812        }
1813        ConfigType::Vector => {
1814            let Some(ref element) = u.config_element_type else {
1815                return Err(Error::validate(format!(
1816                    "Config '{}' is type Vector but is missing field 'element'",
1817                    config
1818                )));
1819            };
1820            let Some(max_count) = u.config_max_count else {
1821                return Err(Error::validate(format!(
1822                    "Config '{}' is type Vector but is missing field 'max_count'",
1823                    config
1824                )));
1825            };
1826            ConfigValueType::Vector {
1827                max_count: max_count.into(),
1828                element: element.clone(),
1829                mutability: None,
1830            }
1831        }
1832    };
1833    Ok(config_type)
1834}
1835
1836/// Given an iterator with `(key, name)` tuples, ensure that `key` doesn't
1837/// appear twice. `name` is used in generated error messages.
1838fn ensure_no_duplicate_names<'a, I>(values: I) -> Result<(), Error>
1839where
1840    I: Iterator<Item = (&'a Name, &'a str)>,
1841{
1842    let mut seen_keys = HashMap::new();
1843    for (key, name) in values {
1844        if let Some(preexisting_name) = seen_keys.insert(key, name) {
1845            return Err(Error::validate(format!(
1846                "identifier \"{}\" is defined twice, once in \"{}\" and once in \"{}\"",
1847                key, name, preexisting_name
1848            )));
1849        }
1850    }
1851    Ok(())
1852}
1853
1854/// Returns an error if the iterator contains duplicate values.
1855fn ensure_no_duplicate_values<'a, I, V>(values: I) -> Result<(), Error>
1856where
1857    I: IntoIterator<Item = &'a V>,
1858    V: 'a + Hash + Eq + fmt::Display,
1859{
1860    let mut seen = HashSet::new();
1861    for value in values {
1862        if !seen.insert(value) {
1863            return Err(Error::validate(format!("Found duplicate value \"{}\" in array.", value)));
1864        }
1865    }
1866    Ok(())
1867}
1868
1869/// A node in the DependencyGraph. This enum is used to differentiate between node types.
1870#[derive(Copy, Clone, Hash, Ord, Debug, PartialOrd, PartialEq, Eq)]
1871enum DependencyNode<'a> {
1872    Named(&'a Name),
1873    Self_,
1874}
1875
1876impl<'a> DependencyNode<'a> {
1877    fn from_dictionary_ref(d: &'a DictionaryRef) -> Option<DependencyNode<'a>> {
1878        match &d.root {
1879            RootDictionaryRef::Named(name) => Some(DependencyNode::Named(name)),
1880            RootDictionaryRef::Self_ => Some(DependencyNode::Self_),
1881            RootDictionaryRef::Parent => None,
1882        }
1883    }
1884
1885    fn offer_to_ref(ref_: &'a OfferToRef) -> DependencyNode<'a> {
1886        match ref_ {
1887            OfferToRef::Named(name) => DependencyNode::Named(name),
1888            OfferToRef::All => panic!(r#"offer to "all" may not be in Dependency Graph"#),
1889            OfferToRef::OwnDictionary(name) => DependencyNode::Named(name),
1890        }
1891    }
1892
1893    fn registration_ref(ref_: &'a RegistrationRef) -> Option<DependencyNode<'a>> {
1894        match ref_ {
1895            RegistrationRef::Named(name) => Some(DependencyNode::Named(name)),
1896            RegistrationRef::Self_ => Some(DependencyNode::Self_),
1897
1898            // We don't care about cycles with the parent, because those will be resolved when the
1899            // parent manifest is validated.
1900            RegistrationRef::Parent => None,
1901        }
1902    }
1903}
1904
1905impl<'a> fmt::Display for DependencyNode<'a> {
1906    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1907        match self {
1908            DependencyNode::Self_ => write!(f, "self"),
1909            DependencyNode::Named(name) => write!(f, "#{}", name),
1910        }
1911    }
1912}
1913
1914#[cfg(test)]
1915mod tests {
1916    use super::*;
1917    use crate::error::Location;
1918    use crate::{
1919        offer_to_all_and_component_diff_capabilities_message,
1920        offer_to_all_and_component_diff_sources_message,
1921    };
1922    use assert_matches::assert_matches;
1923    use serde_json::json;
1924
1925    macro_rules! test_validate_cml {
1926        (
1927            $(
1928                $test_name:ident($input:expr, $($pattern:tt)+),
1929            )+
1930        ) => {
1931            $(
1932                #[test]
1933                fn $test_name() {
1934                    let input = format!("{}", $input);
1935                    let result = validate_for_test("test.cml", &input.as_bytes());
1936                    assert_matches!(result, $($pattern)+);
1937                }
1938            )+
1939        }
1940    }
1941
1942    macro_rules! test_validate_cml_with_feature {
1943        (
1944            $features:expr,
1945            {
1946                $(
1947                    $test_name:ident($input:expr, $($pattern:tt)+),
1948                )+
1949            }
1950        ) => {
1951            $(
1952                #[test]
1953                fn $test_name() {
1954                    let input = format!("{}", $input);
1955                    let features = $features;
1956                    let result = validate_with_features_for_test("test.cml", &input.as_bytes(), &features, &vec![], &vec![], &vec![]);
1957                    assert_matches!(result, $($pattern)+);
1958                }
1959            )+
1960        }
1961    }
1962
1963    fn validate_for_test(filename: &str, input: &[u8]) -> Result<(), Error> {
1964        validate_with_features_for_test(filename, input, &FeatureSet::empty(), &[], &[], &[])
1965    }
1966
1967    fn validate_with_features_for_test(
1968        filename: &str,
1969        input: &[u8],
1970        features: &FeatureSet,
1971        required_offers: &[String],
1972        required_uses: &[String],
1973        required_dictionary_offers: &[String],
1974    ) -> Result<(), Error> {
1975        let input = format!("{}", std::str::from_utf8(input).unwrap().to_string());
1976        let file = Path::new(filename);
1977        let document = crate::parse_one_document(&input, &file)?;
1978        validate_cml(
1979            &document,
1980            Some(&file),
1981            &features,
1982            &CapabilityRequirements {
1983                must_offer: &required_offers
1984                    .iter()
1985                    .map(|value| OfferToAllCapability::Protocol(value))
1986                    .chain(
1987                        required_dictionary_offers
1988                            .iter()
1989                            .map(|value| OfferToAllCapability::Dictionary(value)),
1990                    )
1991                    .collect::<Vec<_>>(),
1992                must_use: &required_uses
1993                    .iter()
1994                    .map(|value| MustUseRequirement::Protocol(value))
1995                    .collect::<Vec<_>>(),
1996            },
1997        )
1998    }
1999
2000    fn unused_component_err_message(missing: &str) -> String {
2001        format!(r#"Protocol "{}" is not used by a component but is required by all"#, missing)
2002    }
2003
2004    #[test]
2005    fn must_use_protocol() {
2006        let input = r##"{
2007            children: [
2008                {
2009                    name: "logger",
2010                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
2011                },
2012                {
2013                    name: "something",
2014                    url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
2015                },
2016            ],
2017        }"##;
2018
2019        let result = validate_with_features_for_test(
2020            "test.cml",
2021            input.as_bytes(),
2022            &FeatureSet::empty(),
2023            &[],
2024            &vec!["fuchsia.logger.LogSink".into()],
2025            &[],
2026        );
2027
2028        assert_matches!(result,
2029            Err(Error::Validate { err, filename }) => {
2030                assert_eq!(err, unused_component_err_message("fuchsia.logger.LogSink"));
2031                assert!(filename.is_some(), "Expected there to be a filename in error message");
2032            }
2033        );
2034
2035        let input = r##"{
2036            children: [
2037                {
2038                    name: "logger",
2039                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
2040                },
2041            ],
2042
2043            use: [
2044                {
2045                    protocol: [ "fuchsia.component.Binder" ],
2046                    from: "framework",
2047                }
2048            ],
2049        }"##;
2050
2051        let result = validate_with_features_for_test(
2052            "test.cml",
2053            input.as_bytes(),
2054            &FeatureSet::empty(),
2055            &[],
2056            &vec!["fuchsia.component.Binder".into()],
2057            &[],
2058        );
2059        assert_matches!(result, Ok(_));
2060    }
2061
2062    #[test]
2063    fn required_offer_to_all() {
2064        let input = r##"{
2065           children: [
2066               {
2067                   name: "logger",
2068                   url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
2069               },
2070               {
2071                   name: "something",
2072                   url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
2073               },
2074           ],
2075           collections: [
2076               {
2077                   name: "coll",
2078                   durability: "transient",
2079               },
2080           ],
2081           offer: [
2082               {
2083                   protocol: "fuchsia.logger.LogSink",
2084                   from: "parent",
2085                   to: "all"
2086               },
2087               {
2088                   protocol: "fuchsia.inspect.InspectSink",
2089                   from: "parent",
2090                   to: "all"
2091               },
2092               {
2093                   protocol: "fuchsia.process.Launcher",
2094                   from: "parent",
2095                   to: "#something",
2096               },
2097           ]
2098       }"##;
2099        let result = validate_with_features_for_test(
2100            "test.cml",
2101            input.as_bytes(),
2102            &FeatureSet::empty(),
2103            &vec!["fuchsia.logger.LogSink".into(), "fuchsia.inspect.InspectSink".into()],
2104            &Vec::new(),
2105            &[],
2106        );
2107        assert_matches!(result, Ok(_));
2108    }
2109
2110    #[test]
2111    fn required_offer_to_all_manually() {
2112        let input = r##"{
2113            children: [
2114                {
2115                    name: "logger",
2116                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
2117                },
2118                {
2119                    name: "something",
2120                    url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
2121                },
2122            ],
2123            collections: [
2124                {
2125                    name: "coll",
2126                    durability: "transient",
2127                },
2128            ],
2129            offer: [
2130                {
2131                    protocol: "fuchsia.logger.LogSink",
2132                    from: "#something",
2133                    to: "#logger"
2134                },
2135                {
2136                    protocol: "fuchsia.logger.LogSink",
2137                    from: "parent",
2138                    to: "#something"
2139                },
2140                {
2141                    protocol: "fuchsia.logger.LogSink",
2142                    from: "parent",
2143                    to: "#coll",
2144                },
2145            ]
2146        }"##;
2147        let result = validate_with_features_for_test(
2148            "test.cml",
2149            input.as_bytes(),
2150            &FeatureSet::empty(),
2151            &vec!["fuchsia.logger.LogSink".into()],
2152            &[],
2153            &[],
2154        );
2155        assert_matches!(result, Ok(_));
2156
2157        let input = r##"{
2158            children: [
2159                {
2160                    name: "logger",
2161                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
2162                },
2163                {
2164                    name: "something",
2165                    url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
2166                },
2167                {
2168                    name: "something_v2",
2169                    url: "fuchsia-pkg://fuchsia.com/something_v2#meta/something_v2.cm",
2170                },
2171            ],
2172            collections: [
2173                {
2174                    name: "coll",
2175                    durability: "transient",
2176                },
2177            ],
2178            offer: [
2179                {
2180                    protocol: "fuchsia.logger.LogSink",
2181                    from: "parent",
2182                    to: ["#logger", "#something", "#something_v2", "#coll"],
2183                },
2184            ]
2185        }"##;
2186        let result = validate_with_features_for_test(
2187            "test.cml",
2188            input.as_bytes(),
2189            &FeatureSet::empty(),
2190            &vec!["fuchsia.logger.LogSink".into()],
2191            &[],
2192            &[],
2193        );
2194        assert_matches!(result, Ok(_));
2195    }
2196
2197    #[test]
2198    fn offer_to_all_mixed_with_array_syntax() {
2199        let input = r##"{
2200                "children": [
2201                    {
2202                        "name": "something",
2203                        "url": "fuchsia-pkg://fuchsia.com/something/stable#meta/something.cm",
2204                    },
2205                ],
2206                "offer": [
2207                    {
2208                        "protocol": ["fuchsia.logger.LogSink", "fuchsia.inspect.InspectSink",],
2209                        "from": "parent",
2210                        "to": "#something",
2211                    },
2212                    {
2213                        "protocol": "fuchsia.logger.LogSink",
2214                        "from": "parent",
2215                        "to": "all",
2216                    },
2217                ],
2218        }"##;
2219
2220        let result = validate_with_features_for_test(
2221            "test.cml",
2222            input.as_bytes(),
2223            &FeatureSet::empty(),
2224            &vec!["fuchsia.logger.LogSink".into()],
2225            &Vec::new(),
2226            &[],
2227        );
2228
2229        assert_matches!(result, Ok(_));
2230
2231        let input = r##"{
2232
2233                "children": [
2234                    {
2235                        "name": "something",
2236                        "url": "fuchsia-pkg://fuchsia.com/something/stable#meta/something.cm",
2237                    },
2238                ],
2239                "offer": [
2240                    {
2241                        "protocol": ["fuchsia.logger.LogSink", "fuchsia.inspect.InspectSink",],
2242                        "from": "parent",
2243                        "to": "all",
2244                    },
2245                    {
2246                        "protocol": "fuchsia.logger.LogSink",
2247                        "from": "parent",
2248                        "to": "#something",
2249                    },
2250                ],
2251        }"##;
2252
2253        let result = validate_with_features_for_test(
2254            "test.cml",
2255            input.as_bytes(),
2256            &FeatureSet::empty(),
2257            &vec!["fuchsia.logger.LogSink".into()],
2258            &Vec::new(),
2259            &[],
2260        );
2261
2262        assert_matches!(result, Ok(_));
2263    }
2264
2265    #[test]
2266    fn offer_to_all_and_manual() {
2267        let input = r##"{
2268            children: [
2269                {
2270                    name: "logger",
2271                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
2272                },
2273                {
2274                    name: "something",
2275                    url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
2276                },
2277            ],
2278            offer: [
2279                {
2280                    protocol: "fuchsia.logger.LogSink",
2281                    from: "parent",
2282                    to: "all"
2283                },
2284                {
2285                    protocol: "fuchsia.logger.LogSink",
2286                    from: "parent",
2287                    to: "#something"
2288                },
2289            ]
2290        }"##;
2291
2292        let result = validate_with_features_for_test(
2293            "test.cml",
2294            input.as_bytes(),
2295            &FeatureSet::empty(),
2296            &vec!["fuchsia.logger.LogSink".into()],
2297            &Vec::new(),
2298            &[],
2299        );
2300
2301        // exact duplication is allowed
2302        assert_matches!(result, Ok(_));
2303
2304        let input = r##"{
2305            children: [
2306                {
2307                    name: "logger",
2308                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
2309                },
2310                {
2311                    name: "something",
2312                    url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
2313                },
2314            ],
2315            offer: [
2316                {
2317                    protocol: "fuchsia.logger.LogSink",
2318                    from: "parent",
2319                    to: "all"
2320                },
2321                {
2322                    protocol: "fuchsia.logger.FakLog",
2323                    from: "parent",
2324                    as: "fuchsia.logger.LogSink",
2325                    to: "#something"
2326                },
2327            ]
2328        }"##;
2329
2330        let result = validate_with_features_for_test(
2331            "test.cml",
2332            input.as_bytes(),
2333            &FeatureSet::empty(),
2334            &vec!["fuchsia.logger.LogSink".into()],
2335            &Vec::new(),
2336            &[],
2337        );
2338
2339        // aliased duplications are forbidden
2340        assert_matches!(result,
2341            Err(Error::Validate { err, filename }) => {
2342                assert_eq!(
2343                    err,
2344                    offer_to_all_and_component_diff_capabilities_message([OfferToAllCapability::Protocol("fuchsia.logger.LogSink")].into_iter(), "something"),
2345                );
2346                assert!(filename.is_some(), "Expected there to be a filename in error message");
2347            }
2348        );
2349
2350        let input = r##"{
2351            children: [
2352                {
2353                    name: "logger",
2354                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
2355                },
2356                {
2357                    name: "something",
2358                    url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
2359                },
2360            ],
2361            offer: [
2362                {
2363                    protocol: "fuchsia.logger.LogSink",
2364                    from: "parent",
2365                    to: "all"
2366                },
2367                {
2368                    protocol: "fuchsia.logger.LogSink",
2369                    from: "framework",
2370                    to: "#something"
2371                },
2372            ]
2373        }"##;
2374
2375        let result = validate_with_features_for_test(
2376            "test.cml",
2377            input.as_bytes(),
2378            &FeatureSet::empty(),
2379            &vec!["fuchsia.logger.LogSink".into()],
2380            &Vec::new(),
2381            &[],
2382        );
2383
2384        // offering the same protocol without an alias from different sources is forbidden
2385        assert_matches!(result,
2386            Err(Error::Validate { err, filename }) => {
2387                assert_eq!(
2388                    err,
2389                    offer_to_all_and_component_diff_sources_message([OfferToAllCapability::Protocol("fuchsia.logger.LogSink")].into_iter(), "something"),
2390                );
2391                assert!(filename.is_some(), "Expected there to be a filename in error message");
2392            }
2393        );
2394    }
2395
2396    #[test]
2397    fn offer_to_all_and_manual_for_dictionary() {
2398        let input = r##"{
2399            children: [
2400                {
2401                    name: "logger",
2402                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
2403                },
2404                {
2405                    name: "something",
2406                    url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
2407                },
2408            ],
2409            offer: [
2410                {
2411                    dictionary: "diagnostics",
2412                    from: "parent",
2413                    to: "all"
2414                },
2415                {
2416                    dictionary: "diagnostics",
2417                    from: "parent",
2418                    to: "#something"
2419                },
2420            ]
2421        }"##;
2422
2423        let result = validate_with_features_for_test(
2424            "test.cml",
2425            input.as_bytes(),
2426            &FeatureSet::empty(),
2427            &vec![],
2428            &Vec::new(),
2429            &["diagnostics".into()],
2430        );
2431
2432        // exact duplication is allowed
2433        assert_matches!(result, Ok(_));
2434
2435        let input = r##"{
2436            children: [
2437                {
2438                    name: "logger",
2439                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
2440                },
2441                {
2442                    name: "something",
2443                    url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
2444                },
2445            ],
2446            offer: [
2447                {
2448                    dictionary: "diagnostics",
2449                    from: "parent",
2450                    to: "all"
2451                },
2452                {
2453                    dictionary: "FakDictionary",
2454                    from: "parent",
2455                    as: "diagnostics",
2456                    to: "#something"
2457                },
2458            ]
2459        }"##;
2460
2461        let result = validate_with_features_for_test(
2462            "test.cml",
2463            input.as_bytes(),
2464            &FeatureSet::empty(),
2465            &vec![],
2466            &Vec::new(),
2467            &["diagnostics".into()],
2468        );
2469
2470        // aliased duplications are forbidden
2471        assert_matches!(result,
2472            Err(Error::Validate { err, filename }) => {
2473                assert_eq!(
2474                    err,
2475                    offer_to_all_and_component_diff_capabilities_message([OfferToAllCapability::Dictionary("diagnostics")].into_iter(), "something"),
2476                );
2477                assert!(filename.is_some(), "Expected there to be a filename in error message");
2478            }
2479        );
2480
2481        let input = r##"{
2482            children: [
2483                {
2484                    name: "logger",
2485                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
2486                },
2487                {
2488                    name: "something",
2489                    url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
2490                },
2491            ],
2492            offer: [
2493                {
2494                    dictionary: "diagnostics",
2495                    from: "parent",
2496                    to: "all"
2497                },
2498                {
2499                    dictionary: "diagnostics",
2500                    from: "framework",
2501                    to: "#something"
2502                },
2503            ]
2504        }"##;
2505
2506        let result = validate_with_features_for_test(
2507            "test.cml",
2508            input.as_bytes(),
2509            &FeatureSet::empty(),
2510            &vec![],
2511            &Vec::new(),
2512            &["diagnostics".into()],
2513        );
2514
2515        // offering the same dictionary without an alias from different sources is forbidden
2516        assert_matches!(result,
2517            Err(Error::Validate { err, filename }) => {
2518                assert_eq!(
2519                    err,
2520                    offer_to_all_and_component_diff_sources_message([OfferToAllCapability::Dictionary("diagnostics")].into_iter(), "something"),
2521                );
2522                assert!(filename.is_some(), "Expected there to be a filename in error message");
2523            }
2524        );
2525    }
2526
2527    fn offer_to_all_diff_sources_message(protocols: &[&str]) -> String {
2528        format!(r#"Protocol(s) {:?} offered to "all" multiple times"#, protocols)
2529    }
2530
2531    #[test]
2532    fn offer_to_all_from_diff_sources() {
2533        let input = r##"{
2534            children: [
2535                {
2536                    name: "logger",
2537                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
2538                },
2539                {
2540                    name: "something",
2541                    url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
2542                },
2543            ],
2544            offer: [
2545                {
2546                    protocol: "fuchsia.logger.LogSink",
2547                    from: "parent",
2548                    to: "all"
2549                },
2550                {
2551                    protocol: "fuchsia.logger.LogSink",
2552                    from: "framework",
2553                    to: "all"
2554                },
2555            ]
2556        }"##;
2557
2558        let result = validate_with_features_for_test(
2559            "test.cml",
2560            input.as_bytes(),
2561            &FeatureSet::empty(),
2562            &vec!["fuchsia.logger.LogSink".into()],
2563            &Vec::new(),
2564            &[],
2565        );
2566
2567        assert_matches!(result,
2568            Err(Error::Validate { err, filename }) => {
2569                assert_eq!(
2570                    err,
2571                    offer_to_all_diff_sources_message(&["fuchsia.logger.LogSink"]),
2572                );
2573                assert!(filename.is_some(), "Expected there to be a filename in error message");
2574            }
2575        );
2576    }
2577
2578    #[test]
2579    fn offer_to_all_with_aliases() {
2580        let input = r##"{
2581            children: [
2582                {
2583                    name: "logger",
2584                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
2585                },
2586                {
2587                    name: "something",
2588                    url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
2589                },
2590            ],
2591            offer: [
2592                {
2593                    protocol: "fuchsia.logger.LogSink",
2594                    from: "parent",
2595                    to: "all"
2596                },
2597                {
2598                    protocol: "fuchsia.logger.LogSink",
2599                    from: "framework",
2600                    to: "all",
2601                    as: "OtherLogSink",
2602                },
2603                {
2604                    protocol: "fuchsia.logger.LogSink",
2605                    from: "framework",
2606                    to: "#something",
2607                    as: "OtherOtherLogSink",
2608                },
2609                {
2610                    protocol: "fuchsia.logger.LogSink",
2611                    from: "parent",
2612                    to: "#something",
2613                    as: "fuchsia.logger.LogSink",
2614                },
2615            ]
2616        }"##;
2617
2618        let result = validate_with_features_for_test(
2619            "test.cml",
2620            input.as_bytes(),
2621            &FeatureSet::empty(),
2622            &["fuchsia.logger.LogSink".into()],
2623            &[],
2624            &[],
2625        );
2626
2627        assert_matches!(result, Ok(_));
2628    }
2629
2630    #[test]
2631    fn required_dict_offers_accept_aliases() {
2632        let input = r##"{
2633            capabilities: [
2634                {
2635                    dictionary: "test-diagnostics",
2636                }
2637            ],
2638            children: [
2639                {
2640                    name: "something",
2641                    url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
2642                },
2643            ],
2644            offer: [
2645                {
2646                    dictionary: "test-diagnostics",
2647                    from: "self",
2648                    to: "#something",
2649                    as: "diagnostics",
2650                }
2651            ]
2652        }"##;
2653
2654        let result = validate_with_features_for_test(
2655            "test.cml",
2656            input.as_bytes(),
2657            &FeatureSet::empty(),
2658            &[],
2659            &[],
2660            &["diagnostics".into()],
2661        );
2662
2663        assert_matches!(result, Ok(_));
2664    }
2665
2666    fn fail_to_make_required_offer(
2667        protocol: &str,
2668        child_or_collection: &str,
2669        component: &str,
2670    ) -> String {
2671        format!(
2672            r#"Protocol "{}" is not offered to {} "{}" but it is a required offer"#,
2673            protocol, child_or_collection, component
2674        )
2675    }
2676
2677    fn fail_to_make_required_offer_dictionary(
2678        dictionary: &str,
2679        child_or_collection: &str,
2680        component: &str,
2681    ) -> String {
2682        format!(
2683            r#"Dictionary "{}" is not offered to {} "{}" but it is a required offer"#,
2684            dictionary, child_or_collection, component
2685        )
2686    }
2687
2688    #[test]
2689    fn fail_to_offer_to_all_when_required() {
2690        let input = r##"{
2691            children: [
2692                {
2693                    name: "logger",
2694                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
2695                },
2696                {
2697                    name: "something",
2698                    url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
2699                },
2700            ],
2701            offer: [
2702                {
2703                    protocol: "fuchsia.logger.LogSink",
2704                    from: "parent",
2705                    to: "#logger"
2706                },
2707                {
2708                    protocol: "fuchsia.logger.LegacyLog",
2709                    from: "parent",
2710                    to: "#something"
2711                },
2712            ]
2713        }"##;
2714        let result = validate_with_features_for_test(
2715            "test.cml",
2716            input.as_bytes(),
2717            &FeatureSet::empty(),
2718            &vec!["fuchsia.logger.LogSink".into()],
2719            &[],
2720            &[],
2721        );
2722
2723        assert_matches!(result,
2724            Err(Error::Validate { err, filename }) => {
2725                assert_eq!(
2726                    err,
2727                    fail_to_make_required_offer(
2728                        "fuchsia.logger.LogSink",
2729                        "child component",
2730                        "something",
2731                    ),
2732                );
2733                assert!(filename.is_some(), "Expected there to be a filename in error message");
2734            }
2735        );
2736
2737        let input = r##"{
2738            children: [
2739                {
2740                    name: "logger",
2741                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
2742                },
2743            ],
2744            collections: [
2745                {
2746                    name: "coll",
2747                    durability: "transient",
2748                },
2749            ],
2750            offer: [
2751                {
2752                    protocol: "fuchsia.logger.LogSink",
2753                    from: "parent",
2754                    to: "#logger"
2755                },
2756            ]
2757        }"##;
2758        let result = validate_with_features_for_test(
2759            "test.cml",
2760            input.as_bytes(),
2761            &FeatureSet::empty(),
2762            &vec!["fuchsia.logger.LogSink".into()],
2763            &[],
2764            &[],
2765        );
2766
2767        assert_matches!(result,
2768            Err(Error::Validate { err, filename }) => {
2769                assert_eq!(
2770                    err,
2771                    fail_to_make_required_offer("fuchsia.logger.LogSink", "collection", "coll"),
2772                );
2773                assert!(filename.is_some(), "Expected there to be a filename in error message");
2774            }
2775        );
2776    }
2777
2778    #[test]
2779    fn fail_to_offer_dictionary_to_all_when_required() {
2780        let input = r##"{
2781            children: [
2782                {
2783                    name: "logger",
2784                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
2785                },
2786                {
2787                    name: "something",
2788                    url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
2789                },
2790            ],
2791            offer: [
2792                {
2793                    protocol: "fuchsia.logger.LogSink",
2794                    from: "parent",
2795                    to: "all"
2796                },
2797                {
2798                    dictionary: "diagnostics",
2799                    from: "parent",
2800                    to: "#logger"
2801                },
2802                {
2803                    protocol: "fuchsia.logger.LegacyLog",
2804                    from: "parent",
2805                    to: "#something"
2806                },
2807            ]
2808        }"##;
2809        let result = validate_with_features_for_test(
2810            "test.cml",
2811            input.as_bytes(),
2812            &FeatureSet::empty(),
2813            &vec![],
2814            &[],
2815            &["diagnostics".to_string()],
2816        );
2817
2818        assert_matches!(result,
2819            Err(Error::Validate { err, filename }) => {
2820                assert_eq!(
2821                    err,
2822                    fail_to_make_required_offer_dictionary(
2823                        "diagnostics",
2824                        "child component",
2825                        "something",
2826                    ),
2827                );
2828                assert!(filename.is_some(), "Expected there to be a filename in error message");
2829            }
2830        );
2831
2832        let input = r##"{
2833            children: [
2834                {
2835                    name: "logger",
2836                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
2837                },
2838            ],
2839            collections: [
2840                {
2841                    name: "coll",
2842                    durability: "transient",
2843                },
2844            ],
2845            offer: [
2846                {
2847                    protocol: "fuchsia.logger.LogSink",
2848                    from: "parent",
2849                    to: "all"
2850                },
2851                {
2852                    protocol: "diagnostics",
2853                    from: "parent",
2854                    to: "all"
2855                },
2856                {
2857                    dictionary: "diagnostics",
2858                    from: "parent",
2859                    to: "#logger"
2860                },
2861            ]
2862        }"##;
2863        let result = validate_with_features_for_test(
2864            "test.cml",
2865            input.as_bytes(),
2866            &FeatureSet::empty(),
2867            &vec!["fuchsia.logger.LogSink".into()],
2868            &[],
2869            &["diagnostics".to_string()],
2870        );
2871        assert_matches!(result,
2872            Err(Error::Validate { err, filename }) => {
2873                assert_eq!(
2874                    err,
2875                    fail_to_make_required_offer_dictionary("diagnostics", "collection", "coll"),
2876                );
2877                assert!(filename.is_some(), "Expected there to be a filename in error message");
2878            }
2879        );
2880    }
2881
2882    #[test]
2883    fn fail_to_offer_dictionary_to_all_when_required_even_if_protocol_called_diagnostics_offered() {
2884        let input = r##"{
2885            children: [
2886                {
2887                    name: "logger",
2888                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
2889                },
2890                {
2891                    name: "something",
2892                    url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
2893                },
2894            ],
2895            offer: [
2896                {
2897                    protocol: "fuchsia.logger.LogSink",
2898                    from: "parent",
2899                    to: "all"
2900                },
2901                {
2902                    protocol: "diagnostics",
2903                    from: "parent",
2904                    to: "all"
2905                },
2906                {
2907                    protocol: "fuchsia.logger.LegacyLog",
2908                    from: "parent",
2909                    to: "#something"
2910                },
2911            ]
2912        }"##;
2913        let result = validate_with_features_for_test(
2914            "test.cml",
2915            input.as_bytes(),
2916            &FeatureSet::empty(),
2917            &[],
2918            &[],
2919            &["diagnostics".to_string()],
2920        );
2921
2922        assert_matches!(result,
2923            Err(Error::Validate { err, filename }) => {
2924                assert_eq!(
2925                    err,
2926                    fail_to_make_required_offer_dictionary(
2927                        "diagnostics",
2928                        "child component",
2929                        "logger",
2930                    ),
2931                );
2932                assert!(filename.is_some(), "Expected there to be a filename in error message");
2933            }
2934        );
2935    }
2936
2937    #[test]
2938    fn test_validate_invalid_json_fails() {
2939        let result = validate_for_test("test.cml", b"{");
2940        let expected_err = r#" --> 1:2
2941  |
29421 | {
2943  |  ^---
2944  |
2945  = expected identifier or string"#;
2946        assert_matches!(result, Err(Error::Parse { err, .. }) if &err == expected_err);
2947    }
2948
2949    #[test]
2950    fn test_cml_json5() {
2951        let input = r##"{
2952            "expose": [
2953                // Here are some services to expose.
2954                { "protocol": "fuchsia.logger.Log", "from": "#logger", },
2955                { "directory": "blobfs", "from": "#logger", "rights": ["rw*"]},
2956            ],
2957            "children": [
2958                {
2959                    name: 'logger',
2960                    'url': 'fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm',
2961                },
2962            ],
2963        }"##;
2964        let result = validate_for_test("test.cml", input.as_bytes());
2965        assert_matches!(result, Ok(()));
2966    }
2967
2968    #[test]
2969    fn test_cml_error_location() {
2970        let input = r##"{
2971    "use": [
2972        {
2973            "protocol": "foo",
2974            "from": "bad",
2975        },
2976    ],
2977}"##;
2978        let result = validate_for_test("test.cml", input.as_bytes());
2979        assert_matches!(
2980            result,
2981            Err(Error::Parse { err, location: Some(l), filename: Some(f) })
2982                if &err == "invalid value: string \"bad\", expected \"parent\", \"framework\", \"debug\", \"self\", \"#<capability-name>\", \"#<child-name>\", \"#<collection-name>\", dictionary path, or none" &&
2983                l == Location { line: 5, column: 21 } &&
2984                f.ends_with("test.cml")
2985        );
2986    }
2987
2988    test_validate_cml! {
2989        // include
2990        test_cml_empty_include(
2991            json!(
2992                {
2993                    "include": [],
2994                }
2995            ),
2996            Ok(())
2997        ),
2998        test_cml_some_include(
2999            json!(
3000                {
3001                    "include": [ "some.cml" ],
3002                }
3003            ),
3004            Ok(())
3005        ),
3006        test_cml_couple_of_include(
3007            json!(
3008                {
3009                    "include": [ "some1.cml", "some2.cml" ],
3010                }
3011            ),
3012            Ok(())
3013        ),
3014
3015        // program
3016        test_cml_empty_json(
3017            json!({}),
3018            Ok(())
3019        ),
3020        test_cml_program(
3021            json!(
3022                {
3023                    "program": {
3024                        "runner": "elf",
3025                        "binary": "bin/app",
3026                    },
3027                }
3028            ),
3029            Ok(())
3030        ),
3031        test_cml_program_use_runner(
3032            json!(
3033                {
3034                    "program": {
3035                        "binary": "bin/app",
3036                    },
3037                    "use": [
3038                        { "runner": "elf", "from": "parent" }
3039                    ]
3040                }
3041            ),
3042            Ok(())
3043        ),
3044        test_cml_program_use_runner_conflict(
3045            json!(
3046                {
3047                    "program": {
3048                        "runner": "elf",
3049                        "binary": "bin/app",
3050                    },
3051                    "use": [
3052                        { "runner": "elf", "from": "parent" }
3053                    ]
3054                }
3055            ),
3056            Err(Error::Validate { err, .. }) if &err ==
3057                "Component has conflicting runners in `program` block and `use` block."
3058        ),
3059        test_cml_program_no_runner(
3060            json!({"program": { "binary": "bin/app" }}),
3061            Err(Error::Validate { err, .. }) if &err ==
3062                "Component has a `program` block defined, but doesn't specify a `runner`. \
3063                Components need to use a runner to actually execute code."
3064        ),
3065
3066        // use
3067        test_cml_use(
3068            json!({
3069                "use": [
3070                  { "protocol": "CoolFonts", "path": "/svc/MyFonts" },
3071                  { "protocol": "CoolFonts2", "path": "/svc/MyFonts2", "from": "parent/dict" },
3072                  { "protocol": "fuchsia.test.hub.HubReport", "from": "framework" },
3073                  { "protocol": "fuchsia.sys2.StorageAdmin", "from": "#data-storage" },
3074                  { "protocol": ["fuchsia.ui.scenic.Scenic", "fuchsia.logger.LogSink"] },
3075                  {
3076                    "directory": "assets",
3077                    "path": "/data/assets",
3078                    "rights": ["rw*"],
3079                  },
3080                  {
3081                    "directory": "config",
3082                    "from": "parent",
3083                    "path": "/data/config",
3084                    "rights": ["rx*"],
3085                    "subdir": "fonts/all",
3086                  },
3087                  { "storage": "data", "path": "/example" },
3088                  { "storage": "cache", "path": "/tmp" },
3089                  {
3090                   "event_stream": ["started", "stopped", "running"],
3091                   "scope":["#test"],
3092                   "path":"/svc/testpath",
3093                   "from":"parent",
3094                  },
3095                  { "runner": "usain", "from": "parent" }
3096                ],
3097                "capabilities": [
3098                    {
3099                        "storage": "data-storage",
3100                        "from": "parent",
3101                        "backing_dir": "minfs",
3102                        "storage_id": "static_instance_id_or_moniker",
3103                    }
3104                ]
3105            }),
3106            Ok(())
3107        ),
3108        test_cml_expose_event_stream_multiple_as(
3109            json!({
3110                "expose": [
3111                    {
3112                        "event_stream": ["started", "stopped"],
3113                        "from" : "framework",
3114                        "as": "something"
3115                    },
3116                ]
3117            }),
3118            Err(Error::Validate { err, .. }) if &err == "as cannot be used with multiple event streams"
3119        ),
3120        test_cml_offer_event_stream_capability_requested_not_from_framework(
3121            json!({
3122                "offer": [
3123                    {
3124                        "event_stream": ["capability_requested", "stopped"],
3125                        "from" : "parent",
3126                        "to": "#something"
3127                    },
3128                ]
3129            }),
3130            Err(Error::Validate { err, .. }) if &err == "\"#something\" is an \"offer\" target from \"parent\" but it does not appear in \"children\" or \"collections\""
3131        ),
3132        test_cml_offer_event_stream_capability_requested_with_filter(
3133            json!({
3134                "offer": [
3135                    {
3136                        "event_stream": "capability_requested",
3137                        "from" : "framework",
3138                        "to": "#something",
3139                    },
3140                ]
3141            }),
3142            Err(Error::Validate { err, .. }) if &err == "\"#something\" is an \"offer\" target from \"framework\" but it does not appear in \"children\" or \"collections\""
3143        ),
3144        test_cml_offer_event_stream_multiple_as(
3145            json!({
3146                "offer": [
3147                    {
3148                        "event_stream": ["started", "stopped"],
3149                        "from" : "framework",
3150                        "to": "#self",
3151                        "as": "something"
3152                    },
3153                ]
3154            }),
3155            Err(Error::Validate { err, .. }) if &err == "as cannot be used with multiple events"
3156        ),
3157        test_cml_expose_event_stream_from_self(
3158            json!({
3159                "expose": [
3160                    { "event_stream": ["started", "stopped"], "from" : "self" },
3161                ]
3162            }),
3163            Err(Error::Validate { err, .. }) if &err == "Cannot expose event_streams from self"
3164        ),
3165        test_cml_offer_event_stream_from_self(
3166            json!({
3167                "offer": [
3168                    { "event_stream": ["started", "stopped"], "from" : "self", "to": "#self" },
3169                ]
3170            }),
3171            Err(Error::Validate { err, .. }) if &err == "cannot offer an event_stream from self"
3172        ),
3173        test_cml_offer_event_stream_from_anything_else(
3174            json!({
3175                "offer": [
3176                    {
3177                        "event_stream": ["started", "stopped"],
3178                        "from" : "framework",
3179                        "to": "#self"
3180                    },
3181                ]
3182            }),
3183            Err(Error::Validate { err, .. }) if &err == "\"#self\" is an \"offer\" target from \"framework\" but it does not appear in \"children\" or \"collections\""
3184        ),
3185        test_cml_expose_event_stream_to_framework(
3186            json!({
3187                "expose": [
3188                    {
3189                        "event_stream": ["started", "stopped"],
3190                        "from" : "self",
3191                        "to": "framework"
3192                    },
3193                ]
3194            }),
3195            Err(Error::Validate { err, .. }) if &err == "cannot expose an event_stream to framework"
3196        ),
3197        test_cml_expose_event_stream_scope_invalid_component(
3198            json!({
3199                "expose": [
3200                    {
3201                        "event_stream": ["started", "stopped"],
3202                        "from" : "framework",
3203                        "scope":["#invalid_component"]
3204                    },
3205                ]
3206            }),
3207            Err(Error::Validate { err, .. }) if &err == "event_stream scope invalid_component did not match a component or collection in this .cml file."
3208        ),
3209
3210        test_cml_use_from_self(
3211            json!({
3212                "use": [
3213                    {
3214                        "protocol": [ "bar_protocol", "baz_protocol" ],
3215                        "from": "self",
3216                    },
3217                    {
3218                        "directory": "foo_directory",
3219                        "from": "self",
3220                        "path": "/dir",
3221                        "rights": [ "r*" ],
3222                    },
3223                    {
3224                        "service": "foo_service",
3225                        "from": "self",
3226                    },
3227                    {
3228                        "config": "foo_config",
3229                        "type": "bool",
3230                        "key": "k",
3231                        "from": "self",
3232                    },
3233                ],
3234                "capabilities": [
3235                    {
3236                        "protocol": "bar_protocol",
3237                    },
3238                    {
3239                        "protocol": "baz_protocol",
3240                    },
3241                    {
3242                        "directory": "foo_directory",
3243                        "path": "/dir",
3244                        "rights": [ "r*" ],
3245                    },
3246                    {
3247                        "service": "foo_service",
3248                    },
3249                    {
3250                        "config": "foo_config",
3251                        "type": "bool",
3252                    },
3253                ]
3254            }),
3255            Ok(())
3256        ),
3257        test_cml_use_protocol_from_self_missing(
3258            json!({
3259                "use": [
3260                    {
3261                        "protocol": "foo_protocol",
3262                        "from": "self",
3263                    },
3264                ],
3265            }),
3266            Err(Error::Validate { err, .. }) if &err == "protocol \"foo_protocol\" is used from self, so it must be declared as a \"protocol\" in \"capabilities\""
3267        ),
3268        test_cml_use_directory_from_self_missing(
3269            json!({
3270                "use": [
3271                    {
3272                        "directory": "foo_directory",
3273                        "from": "self",
3274                    },
3275                ],
3276            }),
3277            Err(Error::Validate { err, .. }) if &err == "directory \"foo_directory\" is used from self, so it must be declared as a \"directory\" in \"capabilities\""
3278        ),
3279        test_cml_use_service_from_self_missing(
3280            json!({
3281                "use": [
3282                    {
3283                        "service": "foo_service",
3284                        "from": "self",
3285                    },
3286                ],
3287            }),
3288            Err(Error::Validate { err, .. }) if &err == "service \"foo_service\" is used from self, so it must be declared as a \"service\" in \"capabilities\""
3289        ),
3290        test_cml_use_config_from_self_missing(
3291            json!({
3292                "use": [
3293                    {
3294                        "config": "foo_config",
3295                        "from": "self",
3296                    },
3297                ],
3298            }),
3299            Err(Error::Validate { err, .. }) if &err == "config \"foo_config\" is used from self, so it must be declared as a \"config\" in \"capabilities\""
3300        ),
3301        test_cml_use_from_self_missing_dictionary(
3302            json!({
3303                "use": [
3304                    {
3305                        "protocol": "foo_protocol",
3306                        "from": "self/dict/inner",
3307                    },
3308                ],
3309            }),
3310            Err(Error::Validate { err, .. }) if &err == "protocol \"foo_protocol\" is used from \"self/dict/inner\", so \"dict\" must be declared as a \"dictionary\" in \"capabilities\""
3311        ),
3312        test_cml_use_event_stream_duplicate(
3313            json!({
3314                "use": [
3315                    { "event_stream": ["started", "started"], "from" : "parent" },
3316                ]
3317            }),
3318            Err(Error::Parse { err, .. }) if &err == "invalid value: array with duplicate element, expected a name or nonempty array of names, with unique elements"
3319        ),
3320        test_cml_use_event_stream_overlapping_path(
3321            json!({
3322                "use": [
3323                    { "directory": "foobarbaz", "path": "/foo/bar/baz", "rights": [ "r*" ] },
3324                    {
3325                        "event_stream": ["started"],
3326                        "path": "/foo/bar/baz/er",
3327                        "from": "parent",
3328                    },
3329                ],
3330            }),
3331            Err(Error::Validate { err, .. }) if &err == "directory \"/foo/bar/baz\" is a prefix of \"use\" target event_stream \"/foo/bar/baz/er\""
3332        ),
3333        test_cml_use_event_stream_invalid_path(
3334            json!({
3335                "use": [
3336                    {
3337                        "event_stream": ["started"],
3338                        "path": "my_stream",
3339                        "from": "parent",
3340                    },
3341                ],
3342            }),
3343            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"my_stream\", expected a path with leading `/` and non-empty segments, where each segment is no more than fuchsia.io/MAX_NAME_LENGTH bytes in length, cannot be . or .., and cannot contain embedded NULs"
3344        ),
3345        test_cml_use_event_stream_self_ref(
3346            json!({
3347                "use": [
3348                    {
3349                        "event_stream": ["started"],
3350                        "path": "/svc/my_stream",
3351                        "from": "self",
3352                    },
3353                ],
3354            }),
3355            Err(Error::Validate { err, .. }) if &err == "\"from: self\" cannot be used with \"event_stream\""
3356        ),
3357        test_cml_use_runner_debug_ref(
3358            json!({
3359                "use": [
3360                    {
3361                        "runner": "elf",
3362                        "from": "debug",
3363                    },
3364                ],
3365            }),
3366            Err(Error::Validate { err, .. }) if &err == "only \"protocol\" supports source from \"debug\""
3367        ),
3368        test_cml_use_runner_self_ref(
3369            json!({
3370                "use": [
3371                    {
3372                        "runner": "elf",
3373                        "from": "self",
3374                    },
3375                ],
3376            }),
3377            Err(Error::Validate { err, .. }) if &err == "\"from: self\" cannot be used with \"runner\""
3378        ),
3379        test_cml_use_missing_props(
3380            json!({
3381                "use": [ { "path": "/svc/fuchsia.logger.Log" } ]
3382            }),
3383            Err(Error::Validate { err, .. }) if &err == "`use` declaration is missing a capability keyword, one of: \"service\", \"protocol\", \"directory\", \"storage\", \"event_stream\", \"runner\", \"config\""
3384        ),
3385        test_cml_use_from_with_storage(
3386            json!({
3387                "use": [ { "storage": "cache", "from": "parent" } ]
3388            }),
3389            Err(Error::Validate { err, .. }) if &err == "\"from\" cannot be used with \"storage\""
3390        ),
3391        test_cml_use_invalid_from(
3392            json!({
3393                "use": [
3394                  { "protocol": "CoolFonts", "from": "bad" }
3395                ]
3396            }),
3397            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"bad\", expected \"parent\", \"framework\", \"debug\", \"self\", \"#<capability-name>\", \"#<child-name>\", \"#<collection-name>\", dictionary path, or none"
3398        ),
3399        test_cml_use_invalid_from_dictionary(
3400            json!({
3401                "use": [
3402                  { "protocol": "CoolFonts", "from": "bad/dict" }
3403                ]
3404            }),
3405            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"bad/dict\", expected \"parent\", \"framework\", \"debug\", \"self\", \"#<capability-name>\", \"#<child-name>\", \"#<collection-name>\", dictionary path, or none"
3406        ),
3407        test_cml_use_from_missing_capability(
3408            json!({
3409                "use": [
3410                  { "protocol": "fuchsia.sys2.Admin", "from": "#mystorage" }
3411                ]
3412            }),
3413            Err(Error::Validate { err, .. }) if &err == "\"use\" source \"#mystorage\" does not appear in \"children\" or \"capabilities\""
3414        ),
3415        test_cml_use_bad_path(
3416            json!({
3417                "use": [
3418                    {
3419                        "protocol": ["CoolFonts", "FunkyFonts"],
3420                        "path": "/MyFonts"
3421                    }
3422                ]
3423            }),
3424            Err(Error::Validate { err, .. }) if &err == "\"path\" can only be specified when one `protocol` is supplied."
3425        ),
3426        test_cml_use_bad_duplicate_target_names(
3427            json!({
3428                "use": [
3429                  { "protocol": "fuchsia.component.Realm" },
3430                  { "protocol": "fuchsia.component.Realm" },
3431                ],
3432            }),
3433            Err(Error::Validate { err, .. }) if &err == "\"/svc/fuchsia.component.Realm\" is a duplicate \"use\" target protocol"
3434        ),
3435        test_cml_use_empty_protocols(
3436            json!({
3437                "use": [
3438                    {
3439                        "protocol": [],
3440                    },
3441                ],
3442            }),
3443            Err(Error::Parse { err, .. }) if &err == "invalid length 0, expected a name or nonempty array of names, with unique elements"
3444        ),
3445        test_cml_use_bad_subdir(
3446            json!({
3447                "use": [
3448                  {
3449                    "directory": "config",
3450                    "path": "/config",
3451                    "from": "parent",
3452                    "rights": [ "r*" ],
3453                    "subdir": "/",
3454                  },
3455                ]
3456            }),
3457            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"/\", expected a path with no leading `/` and non-empty segments"
3458        ),
3459        test_cml_use_resolver_fails(
3460            json!({
3461                "use": [
3462                    {
3463                        "resolver": "pkg_resolver",
3464                    },
3465                ]
3466            }),
3467            Err(Error::Parse { err, .. }) if err.starts_with("unknown field `resolver`, expected one of")
3468        ),
3469
3470        test_cml_use_disallows_nested_dirs_directory(
3471            json!({
3472                "use": [
3473                    { "directory": "foobar", "path": "/foo/bar", "rights": [ "r*" ] },
3474                    { "directory": "foobarbaz", "path": "/foo/bar/baz", "rights": [ "r*" ] },
3475                ],
3476            }),
3477            Err(Error::Validate { err, .. }) if &err == "directory \"/foo/bar\" is a prefix of \"use\" target directory \"/foo/bar/baz\""
3478        ),
3479        test_cml_use_disallows_nested_dirs_storage(
3480            json!({
3481                "use": [
3482                    { "storage": "foobar", "path": "/foo/bar" },
3483                    { "storage": "foobarbaz", "path": "/foo/bar/baz" },
3484                ],
3485            }),
3486            Err(Error::Validate { err, .. }) if &err == "storage \"/foo/bar\" is a prefix of \"use\" target storage \"/foo/bar/baz\""
3487        ),
3488        test_cml_use_disallows_nested_dirs_directory_and_storage(
3489            json!({
3490                "use": [
3491                    { "directory": "foobar", "path": "/foo/bar", "rights": [ "r*" ] },
3492                    { "storage": "foobarbaz", "path": "/foo/bar/baz" },
3493                ],
3494            }),
3495            Err(Error::Validate { err, .. }) if &err == "directory \"/foo/bar\" is a prefix of \"use\" target storage \"/foo/bar/baz\""
3496        ),
3497        test_cml_use_disallows_common_prefixes_service(
3498            json!({
3499                "use": [
3500                    { "directory": "foobar", "path": "/foo/bar", "rights": [ "r*" ] },
3501                    { "protocol": "fuchsia", "path": "/foo/bar/fuchsia" },
3502                ],
3503            }),
3504            Err(Error::Validate { err, .. }) if &err == "directory \"/foo/bar\" is a prefix of \"use\" target protocol \"/foo/bar/fuchsia\""
3505        ),
3506        test_cml_use_disallows_common_prefixes_protocol(
3507            json!({
3508                "use": [
3509                    { "directory": "foobar", "path": "/foo/bar", "rights": [ "r*" ] },
3510                    { "protocol": "fuchsia", "path": "/foo/bar/fuchsia.2" },
3511                ],
3512            }),
3513            Err(Error::Validate { err, .. }) if &err == "directory \"/foo/bar\" is a prefix of \"use\" target protocol \"/foo/bar/fuchsia.2\""
3514        ),
3515        test_cml_use_disallows_pkg_conflicts_for_directories(
3516            json!({
3517                "use": [
3518                    { "directory": "dir", "path": "/pkg/dir", "rights": [ "r*" ] },
3519                ],
3520            }),
3521            Err(Error::Validate { err, .. }) if &err == "directory \"/pkg/dir\" conflicts with the protected path \"/pkg\", please use this capability with a different path"
3522        ),
3523        test_cml_use_disallows_pkg_conflicts_for_protocols(
3524            json!({
3525                "use": [
3526                    { "protocol": "prot", "path": "/pkg/protocol" },
3527                ],
3528            }),
3529            Err(Error::Validate { err, .. }) if &err == "protocol \"/pkg/protocol\" conflicts with the protected path \"/pkg\", please use this capability with a different path"
3530        ),
3531        test_cml_use_disallows_pkg_conflicts_for_storage(
3532            json!({
3533                "use": [
3534                    { "storage": "store", "path": "/pkg/storage" },
3535                ],
3536            }),
3537            Err(Error::Validate { err, .. }) if &err == "storage \"/pkg/storage\" conflicts with the protected path \"/pkg\", please use this capability with a different path"
3538        ),
3539        test_cml_use_disallows_filter_on_non_events(
3540            json!({
3541                "use": [
3542                    { "directory": "foobar", "path": "/foo/bar", "rights": [ "r*" ], "filter": {"path": "/diagnostics"} },
3543                ],
3544            }),
3545            Err(Error::Validate { err, .. }) if &err == "\"filter\" can only be used with \"event_stream\""
3546        ),
3547        test_cml_availability_not_supported_for_event_streams(
3548            json!({
3549                "use": [
3550                    {
3551                        "event_stream": ["destroyed"],
3552                        "from": "parent",
3553                        "availability": "optional",
3554                    }
3555                ]
3556            }),
3557            Err(Error::Validate { err, .. }) if &err == "\"availability\" cannot be used with \"event_stream\""
3558        ),
3559        test_cml_use_from_child_offer_cycle_strong(
3560            json!({
3561                "capabilities": [
3562                    { "protocol": ["fuchsia.example.Protocol"] },
3563                ],
3564                "children": [
3565                    {
3566                        "name": "child",
3567                        "url": "fuchsia-pkg://fuchsia.com/child#meta/child.cm",
3568                    },
3569                ],
3570                "use": [
3571                    {
3572                        "protocol": "fuchsia.child.Protocol",
3573                        "from": "#child",
3574                    },
3575                ],
3576                "offer": [
3577                    {
3578                        "protocol": "fuchsia.example.Protocol",
3579                        "from": "self",
3580                        "to": [ "#child" ],
3581                    },
3582                ],
3583            }),
3584            Err(Error::Validate { err, .. })
3585                if &err == "Strong dependency cycles were found. Break the cycle by removing a \
3586                            dependency or marking an offer as weak. Cycles: \
3587                            {{#child -> self -> #child}}"
3588        ),
3589        test_cml_use_from_parent_weak(
3590            json!({
3591                "use": [
3592                    {
3593                        "protocol": "fuchsia.parent.Protocol",
3594                        "from": "parent",
3595                        "dependency": "weak",
3596                    },
3597                ],
3598            }),
3599            Err(Error::Validate { err, .. }) if &err == "Only `use` from children can have dependency: \"weak\""
3600        ),
3601        test_cml_use_from_child_offer_cycle_weak(
3602            json!({
3603                "capabilities": [
3604                    { "protocol": ["fuchsia.example.Protocol"] },
3605                    { "service": ["fuchsia.example.Service"] },
3606                ],
3607                "children": [
3608                    {
3609                        "name": "child",
3610                        "url": "fuchsia-pkg://fuchsia.com/child#meta/child.cm",
3611                    },
3612                ],
3613                "use": [
3614                    {
3615                        "protocol": "fuchsia.example.Protocol",
3616                        "from": "#child",
3617                        "dependency": "weak",
3618                    },
3619                    {
3620                        "service": "fuchsia.example.Service",
3621                        "from": "#child",
3622                        "dependency": "weak",
3623                    },
3624                ],
3625                "offer": [
3626                    {
3627                        "protocol": "fuchsia.example.Protocol",
3628                        "from": "self",
3629                        "to": [ "#child" ],
3630                    },
3631                    {
3632                        "service": "fuchsia.example.Service",
3633                        "from": "self",
3634                        "to": [ "#child" ],
3635                    },
3636                ],
3637            }),
3638            Ok(())
3639        ),
3640        test_cml_use_from_child_offer_storage_no_cycle(
3641            json!({
3642                "capabilities": [
3643                    {
3644                        "storage": "cdata",
3645                        "from": "#backend",
3646                        "backing_dir": "blobfs",
3647                        "storage_id": "static_instance_id_or_moniker",
3648                    },
3649                    {
3650                        "storage": "pdata",
3651                        "from": "parent",
3652                        "backing_dir": "blobfs",
3653                        "storage_id": "static_instance_id_or_moniker",
3654                    },
3655                ],
3656                "children": [
3657                    {
3658                        "name": "child",
3659                        "url": "#meta/child.cm",
3660                    },
3661                    {
3662                        "name": "backend",
3663                        "url": "#meta/backend.cm",
3664                    },
3665                ],
3666                "use": [
3667                    {
3668                        "protocol": "fuchsia.example.Protocol",
3669                        "from": "#child",
3670                    },
3671                ],
3672                "offer": [
3673                    {
3674                        "storage": "cdata",
3675                        "from": "self",
3676                        "to": "#child",
3677                    },
3678                    {
3679                        "storage": "pdata",
3680                        "from": "self",
3681                        "to": "#child",
3682                    },
3683                ],
3684            }),
3685            Ok(())
3686        ),
3687        test_cml_use_from_child_offer_storage_cycle(
3688            json!({
3689                "capabilities": [
3690                    {
3691                        "storage": "data",
3692                        "from": "self",
3693                        "backing_dir": "blobfs",
3694                        "storage_id": "static_instance_id_or_moniker",
3695                    },
3696                ],
3697                "children": [
3698                    {
3699                        "name": "child",
3700                        "url": "#meta/child.cm",
3701                    },
3702                ],
3703                "use": [
3704                    {
3705                        "protocol": "fuchsia.example.Protocol",
3706                        "from": "#child",
3707                    },
3708                ],
3709                "offer": [
3710                    {
3711                        "storage": "data",
3712                        "from": "self",
3713                        "to": "#child",
3714                    },
3715                ],
3716            }),
3717            Err(Error::Validate {
3718                err,
3719                ..
3720            }) if &err ==
3721                "Strong dependency cycles were found. Break the cycle by removing a dependency \
3722                or marking an offer as weak. Cycles: {{#child -> self -> #data -> #child}}"
3723        ),
3724
3725        // expose
3726        test_cml_expose(
3727            json!({
3728                "expose": [
3729                    {
3730                        "protocol": "A",
3731                        "from": "self",
3732                    },
3733                    {
3734                        "protocol": ["B", "C"],
3735                        "from": "self",
3736                    },
3737                    {
3738                        "protocol": "D",
3739                        "from": "#mystorage",
3740                    },
3741                    {
3742                        "directory": "blobfs",
3743                        "from": "self",
3744                        "rights": ["r*"],
3745                        "subdir": "blob",
3746                    },
3747                    { "directory": "data", "from": "framework" },
3748                    { "runner": "elf", "from": "#logger",  },
3749                    { "resolver": "pkg_resolver", "from": "#logger" },
3750                ],
3751                "capabilities": [
3752                    { "protocol": ["A", "B", "C"] },
3753                    {
3754                        "directory": "blobfs",
3755                        "path": "/blobfs",
3756                        "rights": ["rw*"],
3757                    },
3758                    {
3759                        "storage": "mystorage",
3760                        "from": "self",
3761                        "backing_dir": "blobfs",
3762                        "storage_id": "static_instance_id_or_moniker",
3763                    }
3764                ],
3765                "children": [
3766                    {
3767                        "name": "logger",
3768                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
3769                    },
3770                ]
3771            }),
3772            Ok(())
3773        ),
3774        test_cml_expose_all_valid_chars(
3775            json!({
3776                "expose": [
3777                    {
3778                        "protocol": "fuchsia.logger.Log",
3779                        "from": "#abcdefghijklmnopqrstuvwxyz0123456789_-.",
3780                    },
3781                ],
3782                "children": [
3783                    {
3784                        "name": "abcdefghijklmnopqrstuvwxyz0123456789_-.",
3785                        "url": "https://www.google.com/gmail"
3786                    },
3787                ],
3788            }),
3789            Ok(())
3790        ),
3791        test_cml_expose_missing_props(
3792            json!({
3793                "expose": [ {} ]
3794            }),
3795            Err(Error::Parse { err, .. }) if &err == "missing field `from`"
3796        ),
3797        test_cml_expose_missing_from(
3798            json!({
3799                "expose": [
3800                    { "protocol": "fuchsia.logger.Log", "from": "#missing" },
3801                ],
3802            }),
3803            Err(Error::Validate { err, .. }) if &err == "\"expose\" source \"#missing\" does not appear in \"children\" or \"capabilities\""
3804        ),
3805        test_cml_expose_duplicate_target_names(
3806            json!({
3807                "capabilities": [
3808                    { "protocol": "logger" },
3809                ],
3810                "expose": [
3811                    { "protocol": "logger", "from": "self", "as": "thing" },
3812                    { "directory": "thing", "from": "#child" , "rights": ["rx*"] },
3813                ],
3814                "children": [
3815                    {
3816                        "name": "child",
3817                        "url": "fuchsia-pkg://fuchsia.com/pkg#comp.cm",
3818                    },
3819                ],
3820            }),
3821            Err(Error::Validate { err, .. }) if &err == "\"thing\" is a duplicate \"expose\" target capability for \"parent\""
3822        ),
3823        test_cml_expose_invalid_multiple_from(
3824            json!({
3825                    "capabilities": [
3826                        { "protocol": "fuchsia.logger.Log" },
3827                    ],
3828                    "expose": [
3829                        {
3830                            "protocol": "fuchsia.logger.Log",
3831                            "from": [ "self", "#logger" ],
3832                        },
3833                    ],
3834                    "children": [
3835                        {
3836                            "name": "logger",
3837                            "url": "fuchsia-pkg://fuchsia.com/logger#meta/logger.cm",
3838                        },
3839                    ]
3840                }),
3841            Err(Error::Validate { err, .. }) if &err == "\"protocol\" capabilities cannot have multiple \"from\" clauses"
3842        ),
3843        test_cml_expose_from_missing_named_source(
3844            json!({
3845                    "expose": [
3846                        {
3847                            "protocol": "fuchsia.logger.Log",
3848                            "from": "#does-not-exist",
3849                        },
3850                    ],
3851                }),
3852            Err(Error::Validate { err, .. }) if &err == "\"expose\" source \"#does-not-exist\" does not appear in \"children\" or \"capabilities\""
3853        ),
3854        test_cml_expose_bad_from(
3855            json!({
3856                "expose": [ {
3857                    "protocol": "fuchsia.logger.Log", "from": "parent"
3858                } ]
3859            }),
3860            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"parent\", expected one or an array of \"framework\", \"self\", \"#<child-name>\", or a dictionary path"
3861        ),
3862        // if "as" is specified, only 1 array item is allowed.
3863        test_cml_expose_bad_as(
3864            json!({
3865                "expose": [
3866                    {
3867                        "protocol": ["A", "B"],
3868                        "from": "#echo_server",
3869                        "as": "thing"
3870                    },
3871                ],
3872                "children": [
3873                    {
3874                        "name": "echo_server",
3875                        "url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo_server.cm"
3876                    }
3877                ]
3878            }),
3879            Err(Error::Validate { err, .. }) if &err == "\"as\" can only be specified when one `protocol` is supplied."
3880        ),
3881        test_cml_expose_empty_protocols(
3882            json!({
3883                "expose": [
3884                    {
3885                        "protocol": [],
3886                        "from": "#child",
3887                        "as": "thing"
3888                    },
3889                ],
3890                "children": [
3891                    {
3892                        "name": "child",
3893                        "url": "fuchsia-pkg://fuchsia.com/pkg#comp.cm",
3894                    },
3895                ],
3896            }),
3897            Err(Error::Parse { err, .. }) if &err == "invalid length 0, expected a name or nonempty array of names, with unique elements"
3898        ),
3899        test_cml_expose_bad_subdir(
3900            json!({
3901                "expose": [
3902                    {
3903                        "directory": "blobfs",
3904                        "from": "self",
3905                        "rights": ["r*"],
3906                        "subdir": "/",
3907                    },
3908                ]
3909            }),
3910            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"/\", expected a path with no leading `/` and non-empty segments"
3911        ),
3912        test_cml_expose_invalid_subdir_to_framework(
3913            json!({
3914                "capabilities": [
3915                    {
3916                        "directory": "foo",
3917                        "rights": ["r*"],
3918                        "path": "/foo",
3919                    },
3920                ],
3921                "expose": [
3922                    {
3923                        "directory": "foo",
3924                        "from": "self",
3925                        "to": "framework",
3926                        "subdir": "blob",
3927                    },
3928                ],
3929                "children": [
3930                    {
3931                        "name": "child",
3932                        "url": "fuchsia-pkg://fuchsia.com/pkg#comp.cm",
3933                    },
3934                ],
3935            }),
3936            Err(Error::Validate { err, .. }) if &err == "`subdir` is not supported for expose to framework. Directly expose the subdirectory instead."
3937        ),
3938        test_cml_expose_from_self(
3939            json!({
3940                "expose": [
3941                    {
3942                        "protocol": "foo_protocol",
3943                        "from": "self",
3944                    },
3945                    {
3946                        "protocol": [ "bar_protocol", "baz_protocol" ],
3947                        "from": "self",
3948                    },
3949                    {
3950                        "directory": "foo_directory",
3951                        "from": "self",
3952                    },
3953                    {
3954                        "runner": "foo_runner",
3955                        "from": "self",
3956                    },
3957                    {
3958                        "resolver": "foo_resolver",
3959                        "from": "self",
3960                    },
3961                ],
3962                "capabilities": [
3963                    {
3964                        "protocol": "foo_protocol",
3965                    },
3966                    {
3967                        "protocol": "bar_protocol",
3968                    },
3969                    {
3970                        "protocol": "baz_protocol",
3971                    },
3972                    {
3973                        "directory": "foo_directory",
3974                        "path": "/dir",
3975                        "rights": [ "r*" ],
3976                    },
3977                    {
3978                        "runner": "foo_runner",
3979                        "path": "/svc/runner",
3980                    },
3981                    {
3982                        "resolver": "foo_resolver",
3983                        "path": "/svc/resolver",
3984                    },
3985                ]
3986            }),
3987            Ok(())
3988        ),
3989        test_cml_expose_protocol_from_self_missing(
3990            json!({
3991                "expose": [
3992                    {
3993                        "protocol": "pkg_protocol",
3994                        "from": "self",
3995                    },
3996                ],
3997            }),
3998            Err(Error::Validate { err, .. }) if &err == "protocol \"pkg_protocol\" is exposed from self, so it must be declared as a \"protocol\" in \"capabilities\""
3999        ),
4000        test_cml_expose_protocol_from_self_missing_multiple(
4001            json!({
4002                "expose": [
4003                    {
4004                        "protocol": [ "foo_protocol", "bar_protocol" ],
4005                        "from": "self",
4006                    },
4007                ],
4008            }),
4009            Err(Error::Validate { err, .. }) if &err == "protocol \"foo_protocol\" is exposed from self, so it must be declared as a \"protocol\" in \"capabilities\""
4010        ),
4011        test_cml_expose_directory_from_self_missing(
4012            json!({
4013                "expose": [
4014                    {
4015                        "directory": "pkg_directory",
4016                        "from": "self",
4017                    },
4018                ],
4019            }),
4020            Err(Error::Validate { err, .. }) if &err == "directory \"pkg_directory\" is exposed from self, so it must be declared as a \"directory\" in \"capabilities\""
4021        ),
4022        test_cml_expose_service_from_self_missing(
4023            json!({
4024                "expose": [
4025                    {
4026                        "service": "pkg_service",
4027                        "from": "self",
4028                    },
4029                ],
4030            }),
4031            Err(Error::Validate { err, .. }) if &err == "service \"pkg_service\" is exposed from self, so it must be declared as a \"service\" in \"capabilities\""
4032        ),
4033        test_cml_expose_runner_from_self_missing(
4034            json!({
4035                "expose": [
4036                    {
4037                        "runner": "dart",
4038                        "from": "self",
4039                    },
4040                ],
4041            }),
4042            Err(Error::Validate { err, .. }) if &err == "runner \"dart\" is exposed from self, so it must be declared as a \"runner\" in \"capabilities\""
4043        ),
4044        test_cml_expose_resolver_from_self_missing(
4045            json!({
4046                "expose": [
4047                    {
4048                        "resolver": "pkg_resolver",
4049                        "from": "self",
4050                    },
4051                ],
4052            }),
4053            Err(Error::Validate { err, .. }) if &err == "resolver \"pkg_resolver\" is exposed from self, so it must be declared as a \"resolver\" in \"capabilities\""
4054        ),
4055        test_cml_expose_from_self_missing_dictionary(
4056            json!({
4057                "expose": [
4058                    {
4059                        "protocol": "foo_protocol",
4060                        "from": "self/dict/inner",
4061                    },
4062                ],
4063            }),
4064            Err(Error::Validate { err, .. }) if &err == "protocol \"foo_protocol\" is exposed from \"self/dict/inner\", so \"dict\" must be declared as a \"dictionary\" in \"capabilities\""
4065        ),
4066        test_cml_expose_from_dictionary_invalid(
4067            json!({
4068                "expose": [
4069                    {
4070                        "protocol": "pkg_protocol",
4071                        "from": "bad/a",
4072                    },
4073                ],
4074            }),
4075            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"bad/a\", expected one or an array of \"framework\", \"self\", \"#<child-name>\", or a dictionary path"
4076        ),
4077        test_cml_expose_from_dictionary_parent(
4078            json!({
4079                "expose": [
4080                    {
4081                        "protocol": "pkg_protocol",
4082                        "from": "parent/a",
4083                    },
4084                ],
4085            }),
4086            Err(Error::Validate { err, .. }) if &err == "`expose` dictionary path must begin with `self` or `#<child-name>`"
4087        ),
4088                test_cml_expose_protocol_from_collection_invalid(
4089            json!({
4090                "collections": [ {
4091                    "name": "coll",
4092                    "durability": "transient",
4093                } ],
4094                "expose": [
4095                    { "protocol": "fuchsia.logger.Log", "from": "#coll" },
4096                ]
4097            }),
4098            Err(Error::Validate { err, .. }) if &err == "\"expose\" source \"#coll\" does not appear in \"children\" or \"capabilities\""
4099        ),
4100        test_cml_expose_directory_from_collection_invalid(
4101            json!({
4102                "collections": [ {
4103                    "name": "coll",
4104                    "durability": "transient",
4105                } ],
4106                "expose": [
4107                    { "directory": "temp", "from": "#coll" },
4108                ]
4109            }),
4110            Err(Error::Validate { err, .. }) if &err == "\"expose\" source \"#coll\" does not appear in \"children\""
4111        ),
4112        test_cml_expose_runner_from_collection_invalid(
4113            json!({
4114                "collections": [ {
4115                    "name": "coll",
4116                    "durability": "transient",
4117                } ],
4118                "expose": [
4119                    { "runner": "elf", "from": "#coll" },
4120                ]
4121            }),
4122            Err(Error::Validate { err, .. }) if &err == "\"expose\" source \"#coll\" does not appear in \"children\""
4123        ),
4124        test_cml_expose_resolver_from_collection_invalid(
4125            json!({
4126                "collections": [ {
4127                    "name": "coll",
4128                    "durability": "transient",
4129                } ],
4130                "expose": [
4131                    { "resolver": "base", "from": "#coll" },
4132                ]
4133            }),
4134            Err(Error::Validate { err, .. }) if &err == "\"expose\" source \"#coll\" does not appear in \"children\""
4135        ),
4136        test_cml_expose_to_framework_ok(
4137            json!({
4138                "capabilities": [
4139                    {
4140                        "directory": "foo",
4141                        "path": "/foo",
4142                        "rights": ["r*"],
4143                    },
4144                ],
4145                "expose": [
4146                    {
4147                        "directory": "foo",
4148                        "from": "self",
4149                        "to": "framework"
4150                    }
4151                ],
4152                "children": [
4153                    {
4154                        "name": "child",
4155                        "url": "fuchsia-pkg://fuchsia.com/pkg#comp.cm",
4156                    },
4157                ],
4158            }),
4159            Ok(())
4160        ),
4161        // offer
4162        test_cml_offer(
4163            json!({
4164                "offer": [
4165                    {
4166                        "protocol": "fuchsia.fonts.LegacyProvider",
4167                        "from": "parent",
4168                        "to": [ "#echo_server" ],
4169                        "dependency": "weak"
4170                    },
4171                    {
4172                        "protocol": "fuchsia.sys2.StorageAdmin",
4173                        "from": "#data",
4174                        "to": [ "#echo_server" ]
4175                    },
4176                    {
4177                        "protocol": [
4178                            "fuchsia.settings.Accessibility",
4179                            "fuchsia.ui.scenic.Scenic"
4180                        ],
4181                        "from": "parent",
4182                        "to": [ "#echo_server" ],
4183                        "dependency": "strong"
4184                    },
4185                    {
4186                        "directory": "assets",
4187                        "from": "self",
4188                        "to": [ "#echo_server" ],
4189                        "rights": ["r*"]
4190                    },
4191                    {
4192                        "directory": "index",
4193                        "subdir": "files",
4194                        "from": "parent",
4195                        "to": [ "#modular" ],
4196                        "dependency": "weak"
4197                    },
4198                    {
4199                        "directory": "config",
4200                        "from": "framework",
4201                        "to": [ "#modular" ],
4202                        "as": "config",
4203                        "dependency": "strong"
4204                    },
4205                    {
4206                        "storage": "data",
4207                        "from": "self",
4208                        "to": [ "#modular", "#logger" ]
4209                    },
4210                    {
4211                        "runner": "elf",
4212                        "from": "parent",
4213                        "to": [ "#modular", "#logger" ]
4214                    },
4215                    {
4216                        "resolver": "pkg_resolver",
4217                        "from": "parent",
4218                        "to": [ "#modular" ],
4219                    },
4220                ],
4221                "children": [
4222                    {
4223                        "name": "logger",
4224                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
4225                    },
4226                    {
4227                        "name": "echo_server",
4228                        "url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo_server.cm"
4229                    },
4230                ],
4231                "collections": [
4232                    {
4233                        "name": "modular",
4234                        "durability": "transient",
4235                    },
4236                ],
4237                "capabilities": [
4238                    {
4239                        "directory": "assets",
4240                        "path": "/data/assets",
4241                        "rights": [ "rw*" ],
4242                    },
4243                    {
4244                        "storage": "data",
4245                        "from": "parent",
4246                        "backing_dir": "minfs",
4247                        "storage_id": "static_instance_id_or_moniker",
4248                    },
4249                ],
4250            }),
4251            Ok(())
4252        ),
4253        test_cml_offer_all_valid_chars(
4254            json!({
4255                "offer": [
4256                    {
4257                        "protocol": "fuchsia.logger.Log",
4258                        "from": "#abcdefghijklmnopqrstuvwxyz0123456789_-from",
4259                        "to": [ "#abcdefghijklmnopqrstuvwxyz0123456789_-to" ],
4260                    },
4261                ],
4262                "children": [
4263                    {
4264                        "name": "abcdefghijklmnopqrstuvwxyz0123456789_-from",
4265                        "url": "https://www.google.com/gmail"
4266                    },
4267                    {
4268                        "name": "abcdefghijklmnopqrstuvwxyz0123456789_-to",
4269                        "url": "https://www.google.com/gmail"
4270                    },
4271                ],
4272                "capabilities": [
4273                    {
4274                        "storage": "abcdefghijklmnopqrstuvwxyz0123456789_-storage",
4275                        "from": "#abcdefghijklmnopqrstuvwxyz0123456789_-from",
4276                        "backing_dir": "example",
4277                        "storage_id": "static_instance_id_or_moniker",
4278                    }
4279                ]
4280            }),
4281            Ok(())
4282        ),
4283        test_cml_offer_singleton_to (
4284            json!({
4285                "offer": [
4286                    {
4287                        "protocol": "fuchsia.fonts.LegacyProvider",
4288                        "from": "parent",
4289                        "to": "#echo_server",
4290                        "dependency": "weak"
4291                    },
4292                ],
4293                "children": [
4294                    {
4295                        "name": "echo_server",
4296                        "url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo_server.cm"
4297                    },
4298                ],
4299            }),
4300            Ok(())
4301        ),
4302        test_cml_offer_missing_props(
4303            json!({
4304                "offer": [ {} ]
4305            }),
4306            Err(Error::Parse { err, .. }) if &err == "missing field `from`"
4307        ),
4308        test_cml_offer_missing_from(
4309            json!({
4310                    "offer": [
4311                        {
4312                            "protocol": "fuchsia.logger.Log",
4313                            "from": "#missing",
4314                            "to": [ "#echo_server" ],
4315                        },
4316                    ],
4317                    "children": [
4318                        {
4319                            "name": "echo_server",
4320                            "url": "fuchsia-pkg://fuchsia.com/echo_server#meta/echo_server.cm",
4321                        },
4322                    ],
4323                }),
4324            Err(Error::Validate { err, .. }) if &err == "\"offer\" source \"#missing\" does not appear in \"children\" or \"capabilities\""
4325        ),
4326        test_cml_storage_offer_from_child(
4327            json!({
4328                    "offer": [
4329                        {
4330                            "storage": "cache",
4331                            "from": "#storage_provider",
4332                            "to": [ "#echo_server" ],
4333                        },
4334                    ],
4335                    "children": [
4336                        {
4337                            "name": "echo_server",
4338                            "url": "fuchsia-pkg://fuchsia.com/echo_server#meta/echo_server.cm",
4339                        },
4340                        {
4341                            "name": "storage_provider",
4342                            "url": "fuchsia-pkg://fuchsia.com/storage_provider#meta/storage_provider.cm",
4343                        },
4344                    ],
4345                }),
4346            Err(Error::Validate { err, .. }) if &err == "Storage \"cache\" is offered from a child, but storage capabilities cannot be exposed"
4347        ),
4348        test_cml_offer_bad_from(
4349            json!({
4350                    "offer": [ {
4351                        "protocol": "fuchsia.logger.Log",
4352                        "from": "#invalid@",
4353                        "to": [ "#echo_server" ],
4354                    } ]
4355                }),
4356            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"#invalid@\", expected one or an array of \"parent\", \"framework\", \"self\", \"#<child-name>\", \"#<collection-name>\", or a dictionary path"
4357        ),
4358        test_cml_offer_invalid_multiple_from(
4359            json!({
4360                    "offer": [
4361                        {
4362                            "protocol": "fuchsia.logger.Log",
4363                            "from": [ "parent", "#logger" ],
4364                            "to": [ "#echo_server" ],
4365                        },
4366                    ],
4367                    "children": [
4368                        {
4369                            "name": "logger",
4370                            "url": "fuchsia-pkg://fuchsia.com/logger#meta/logger.cm",
4371                        },
4372                        {
4373                            "name": "echo_server",
4374                            "url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo_server.cm",
4375                        },
4376                    ]
4377                }),
4378            Err(Error::Validate { err, .. }) if &err == "\"protocol\" capabilities cannot have multiple \"from\" clauses"
4379        ),
4380        test_cml_offer_from_missing_named_source(
4381            json!({
4382                    "offer": [
4383                        {
4384                            "protocol": "fuchsia.logger.Log",
4385                            "from": "#does-not-exist",
4386                            "to": ["#echo_server" ],
4387                        },
4388                    ],
4389                    "children": [
4390                        {
4391                            "name": "echo_server",
4392                            "url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo_server.cm",
4393                        },
4394                    ]
4395                }),
4396            Err(Error::Validate { err, .. }) if &err == "\"offer\" source \"#does-not-exist\" does not appear in \"children\" or \"capabilities\""
4397        ),
4398        test_cml_offer_protocol_from_collection_invalid(
4399            json!({
4400                "collections": [ {
4401                    "name": "coll",
4402                    "durability": "transient",
4403                } ],
4404                "children": [ {
4405                    "name": "echo_server",
4406                    "url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo_server.cm",
4407                } ],
4408                "offer": [
4409                    { "protocol": "fuchsia.logger.Log", "from": "#coll", "to": [ "#echo_server" ] },
4410                ]
4411            }),
4412            Err(Error::Validate { err, .. }) if &err == "\"offer\" source \"#coll\" does not appear in \"children\" or \"capabilities\""
4413        ),
4414        test_cml_offer_directory_from_collection_invalid(
4415            json!({
4416                "collections": [ {
4417                    "name": "coll",
4418                    "durability": "transient",
4419                } ],
4420                "children": [ {
4421                    "name": "echo_server",
4422                    "url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo_server.cm",
4423                } ],
4424                "offer": [
4425                    { "directory": "temp", "from": "#coll", "to": [ "#echo_server" ] },
4426                ]
4427            }),
4428            Err(Error::Validate { err, .. }) if &err == "\"offer\" source \"#coll\" does not appear in \"children\""
4429        ),
4430        test_cml_offer_storage_from_collection_invalid(
4431            json!({
4432                "collections": [ {
4433                    "name": "coll",
4434                    "durability": "transient",
4435                } ],
4436                "children": [ {
4437                    "name": "echo_server",
4438                    "url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo_server.cm",
4439                } ],
4440                "offer": [
4441                    { "storage": "cache", "from": "#coll", "to": [ "#echo_server" ] },
4442                ]
4443            }),
4444            Err(Error::Validate { err, .. }) if &err == "Storage \"cache\" is offered from a child, but storage capabilities cannot be exposed"
4445        ),
4446        test_cml_offer_runner_from_collection_invalid(
4447            json!({
4448                "collections": [ {
4449                    "name": "coll",
4450                    "durability": "transient",
4451                } ],
4452                "children": [ {
4453                    "name": "echo_server",
4454                    "url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo_server.cm",
4455                } ],
4456                "offer": [
4457                    { "runner": "elf", "from": "#coll", "to": [ "#echo_server" ] },
4458                ]
4459            }),
4460            Err(Error::Validate { err, .. }) if &err == "\"offer\" source \"#coll\" does not appear in \"children\""
4461        ),
4462        test_cml_offer_resolver_from_collection_invalid(
4463            json!({
4464                "collections": [ {
4465                    "name": "coll",
4466                    "durability": "transient",
4467                } ],
4468                "children": [ {
4469                    "name": "echo_server",
4470                    "url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo_server.cm",
4471                } ],
4472                "offer": [
4473                    { "resolver": "base", "from": "#coll", "to": [ "#echo_server" ] },
4474                ]
4475            }),
4476            Err(Error::Validate { err, .. }) if &err == "\"offer\" source \"#coll\" does not appear in \"children\""
4477        ),
4478        test_cml_offer_from_dictionary_invalid(
4479            json!({
4480                "offer": [
4481                    {
4482                        "protocol": "pkg_protocol",
4483                        "from": "bad/a",
4484                        "to": "#child",
4485                    },
4486                ],
4487                "children": [
4488                    {
4489                        "name": "child",
4490                        "url": "fuchsia-pkg://child",
4491                    },
4492                ],
4493            }),
4494            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"bad/a\", expected one or an array of \"parent\", \"framework\", \"self\", \"#<child-name>\", \"#<collection-name>\", or a dictionary path"
4495        ),
4496        test_cml_offer_to_non_dictionary(
4497            json!({
4498                "offer": [
4499                    {
4500                        "protocol": "p",
4501                        "from": "parent",
4502                        "to": "self/dict",
4503                    },
4504                ],
4505                "capabilities": [
4506                    {
4507                        "protocol": "dict",
4508                    },
4509                ],
4510            }),
4511            Err(Error::Validate { err, .. }) if &err == "\"offer\" has dictionary target \
4512            \"self/dict\" but \"dict\" is not a dictionary capability defined by \
4513            this component"
4514        ),
4515
4516        test_cml_offer_empty_targets(
4517            json!({
4518                "offer": [
4519                    {
4520                        "protocol": "fuchsia.logger.Log",
4521                        "from": "#child",
4522                        "to": []
4523                    },
4524                ],
4525                "children": [
4526                    {
4527                        "name": "child",
4528                        "url": "fuchsia-pkg://fuchsia.com/pkg#comp.cm",
4529                    },
4530                ],
4531            }),
4532            Err(Error::Parse { err, .. }) if &err == "invalid length 0, expected one or an array of \"#<child-name>\", \"#<collection-name>\", or \"self/<dictionary>\", with unique elements"
4533        ),
4534        test_cml_offer_duplicate_targets(
4535            json!({
4536                "offer": [ {
4537                    "protocol": "fuchsia.logger.Log",
4538                    "from": "#logger",
4539                    "to": ["#a", "#a"]
4540                } ]
4541            }),
4542            Err(Error::Parse { err, .. }) if &err == "invalid value: array with duplicate element, expected one or an array of \"#<child-name>\", \"#<collection-name>\", or \"self/<dictionary>\", with unique elements"
4543        ),
4544        test_cml_offer_target_missing_props(
4545            json!({
4546                "offer": [ {
4547                    "protocol": "fuchsia.logger.Log",
4548                    "from": "#logger",
4549                    "as": "fuchsia.logger.SysLog",
4550                } ]
4551            }),
4552            Err(Error::Parse { err, .. }) if &err == "missing field `to`"
4553        ),
4554        test_cml_offer_target_missing_to(
4555            json!({
4556                "offer": [ {
4557                    "protocol": "fuchsia.logger.Log",
4558                    "from": "#logger",
4559                    "to": [ "#missing" ],
4560                } ],
4561                "children": [ {
4562                    "name": "logger",
4563                    "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
4564                } ]
4565            }),
4566            Err(Error::Validate { err, .. }) if &err == "\"#missing\" is an \"offer\" target from \"#logger\" but it does not appear in \"children\" or \"collections\""
4567        ),
4568        test_cml_offer_target_bad_to(
4569            json!({
4570                "offer": [ {
4571                    "protocol": "fuchsia.logger.Log",
4572                    "from": "#logger",
4573                    "to": [ "self" ],
4574                    "as": "fuchsia.logger.SysLog",
4575                } ]
4576            }),
4577            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"self\", expected \"#<child-name>\", \"#<collection-name>\", or \"self/<dictionary>\""
4578        ),
4579        test_cml_offer_empty_protocols(
4580            json!({
4581                "offer": [
4582                    {
4583                        "protocol": [],
4584                        "from": "parent",
4585                        "to": [ "#echo_server" ],
4586                        "as": "thing"
4587                    },
4588                ],
4589            }),
4590            Err(Error::Parse { err, .. }) if &err == "invalid length 0, expected a name or nonempty array of names, with unique elements"
4591        ),
4592        test_cml_offer_target_equals_from(
4593            json!({
4594                "children": [
4595                    {
4596                        "name": "child",
4597                        "url": "fuchsia-pkg://fuchsia.com/child#meta/child.cm",
4598                    },
4599                ],
4600                "offer": [
4601                    {
4602                        "protocol": "fuchsia.example.Protocol",
4603                        "from": "#child",
4604                        "to": [ "#child" ],
4605                    },
4606                ],
4607            }),
4608            Err(Error::Validate { err, .. }) if &err == "Offer target \"#child\" is same as source"
4609        ),
4610        test_cml_offer_target_equals_from_weak(
4611            json!({
4612                "children": [
4613                    {
4614                        "name": "child",
4615                        "url": "fuchsia-pkg://fuchsia.com/child#meta/child.cm",
4616                    },
4617                ],
4618                "offer": [
4619                    {
4620                        "protocol": "fuchsia.example.Protocol",
4621                        "from": "#child",
4622                        "to": [ "#child" ],
4623                        "dependency": "weak",
4624                    },
4625                    {
4626                        "directory": "data",
4627                        "from": "#child",
4628                        "to": [ "#child" ],
4629                        "dependency": "weak",
4630                    },
4631                ],
4632            }),
4633            Ok(())
4634        ),
4635        test_cml_storage_offer_target_equals_from(
4636            json!({
4637                "offer": [ {
4638                    "storage": "minfs",
4639                    "from": "self",
4640                    "to": [ "#logger" ],
4641                } ],
4642                "children": [ {
4643                    "name": "logger",
4644                    "url": "fuchsia-pkg://fuchsia.com/logger#meta/logger.cm",
4645                } ],
4646                "capabilities": [ {
4647                    "storage": "minfs",
4648                    "from": "#logger",
4649                    "backing_dir": "minfs-dir",
4650                    "storage_id": "static_instance_id_or_moniker",
4651                } ],
4652            }),
4653            Err(Error::Validate { err, .. }) if &err == "Storage offer target \"#logger\" is same as source"
4654        ),
4655        test_cml_offer_duplicate_target_names(
4656            json!({
4657                "offer": [
4658                    {
4659                        "protocol": "logger",
4660                        "from": "parent",
4661                        "to": [ "#echo_server" ],
4662                        "as": "thing"
4663                    },
4664                    {
4665                        "protocol": "logger",
4666                        "from": "parent",
4667                        "to": [ "#scenic" ],
4668                    },
4669                    {
4670                        "directory": "thing",
4671                        "from": "parent",
4672                        "to": [ "#echo_server" ],
4673                    }
4674                ],
4675                "children": [
4676                    {
4677                        "name": "scenic",
4678                        "url": "fuchsia-pkg://fuchsia.com/scenic/stable#meta/scenic.cm"
4679                    },
4680                    {
4681                        "name": "echo_server",
4682                        "url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo_server.cm"
4683                    },
4684                ],
4685            }),
4686            Err(Error::Validate { err, .. }) if &err == "\"thing\" is a duplicate \"offer\" target capability for \"#echo_server\""
4687        ),
4688        test_cml_offer_duplicate_storage_names(
4689            json!({
4690                "offer": [
4691                    {
4692                        "storage": "cache",
4693                        "from": "parent",
4694                        "to": [ "#echo_server" ]
4695                    },
4696                    {
4697                        "storage": "cache",
4698                        "from": "self",
4699                        "to": [ "#echo_server" ]
4700                    }
4701                ],
4702                "capabilities": [ {
4703                    "storage": "cache",
4704                    "from": "self",
4705                    "backing_dir": "minfs",
4706                    "storage_id": "static_instance_id_or_moniker",
4707                } ],
4708                "children": [ {
4709                    "name": "echo_server",
4710                    "url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo_server.cm"
4711                } ]
4712            }),
4713            Err(Error::Validate { err, .. }) if &err == "\"cache\" is a duplicate \"offer\" target capability for \"#echo_server\""
4714        ),
4715        // if "as" is specified, only 1 array item is allowed.
4716        test_cml_offer_bad_as(
4717            json!({
4718                "offer": [
4719                    {
4720                        "protocol": ["A", "B"],
4721                        "from": "parent",
4722                        "to": [ "#echo_server" ],
4723                        "as": "thing"
4724                    },
4725                ],
4726                "children": [
4727                    {
4728                        "name": "echo_server",
4729                        "url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo_server.cm"
4730                    }
4731                ]
4732            }),
4733            Err(Error::Validate { err, .. }) if &err == "\"as\" can only be specified when one `protocol` is supplied."
4734        ),
4735        test_cml_offer_bad_subdir(
4736            json!({
4737                "offer": [
4738                    {
4739                        "directory": "index",
4740                        "subdir": "/",
4741                        "from": "parent",
4742                        "to": [ "#modular" ],
4743                    },
4744                ],
4745                "children": [
4746                    {
4747                        "name": "modular",
4748                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
4749                    }
4750                ]
4751            }),
4752            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"/\", expected a path with no leading `/` and non-empty segments"
4753        ),
4754        test_cml_offer_from_self(
4755            json!({
4756                "offer": [
4757                    {
4758                        "protocol": "foo_protocol",
4759                        "from": "self",
4760                        "to": [ "#modular" ],
4761                    },
4762                    {
4763                        "protocol": [ "bar_protocol", "baz_protocol" ],
4764                        "from": "self",
4765                        "to": [ "#modular" ],
4766                    },
4767                    {
4768                        "directory": "foo_directory",
4769                        "from": "self",
4770                        "to": [ "#modular" ],
4771                    },
4772                    {
4773                        "runner": "foo_runner",
4774                        "from": "self",
4775                        "to": [ "#modular" ],
4776                    },
4777                    {
4778                        "resolver": "foo_resolver",
4779                        "from": "self",
4780                        "to": [ "#modular" ],
4781                    },
4782                ],
4783                "children": [
4784                    {
4785                        "name": "modular",
4786                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
4787                    },
4788                ],
4789                "capabilities": [
4790                    {
4791                        "protocol": "foo_protocol",
4792                    },
4793                    {
4794                        "protocol": "bar_protocol",
4795                    },
4796                    {
4797                        "protocol": "baz_protocol",
4798                    },
4799                    {
4800                        "directory": "foo_directory",
4801                        "path": "/dir",
4802                        "rights": [ "r*" ],
4803                    },
4804                    {
4805                        "runner": "foo_runner",
4806                        "path": "/svc/fuchsia.sys2.ComponentRunner",
4807                    },
4808                    {
4809                        "resolver": "foo_resolver",
4810                        "path": "/svc/fuchsia.component.resolution.Resolver",
4811                    },
4812                ]
4813            }),
4814            Ok(())
4815        ),
4816        test_cml_offer_service_from_self_missing(
4817            json!({
4818                "offer": [
4819                    {
4820                        "service": "pkg_service",
4821                        "from": "self",
4822                        "to": [ "#modular" ],
4823                    },
4824                ],
4825                "children": [
4826                    {
4827                        "name": "modular",
4828                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
4829                    },
4830                ],
4831            }),
4832            Err(Error::Validate { err, .. }) if &err == "service \"pkg_service\" is offered from self, so it must be declared as a \"service\" in \"capabilities\""
4833        ),
4834        test_cml_offer_protocol_from_self_missing(
4835            json!({
4836                "offer": [
4837                    {
4838                        "protocol": "pkg_protocol",
4839                        "from": "self",
4840                        "to": [ "#modular" ],
4841                    },
4842                ],
4843                "children": [
4844                    {
4845                        "name": "modular",
4846                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
4847                    },
4848                ],
4849            }),
4850            Err(Error::Validate { err, .. }) if &err == "protocol \"pkg_protocol\" is offered from self, so it must be declared as a \"protocol\" in \"capabilities\""
4851        ),
4852        test_cml_offer_protocol_from_self_missing_multiple(
4853            json!({
4854                "offer": [
4855                    {
4856                        "protocol": [ "foo_protocol", "bar_protocol" ],
4857                        "from": "self",
4858                        "to": [ "#modular" ],
4859                    },
4860                ],
4861                "children": [
4862                    {
4863                        "name": "modular",
4864                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
4865                    },
4866                ],
4867            }),
4868            Err(Error::Validate { err, .. }) if &err == "protocol \"foo_protocol\" is offered from self, so it must be declared as a \"protocol\" in \"capabilities\""
4869        ),
4870        test_cml_offer_directory_from_self_missing(
4871            json!({
4872                "offer": [
4873                    {
4874                        "directory": "pkg_directory",
4875                        "from": "self",
4876                        "to": [ "#modular" ],
4877                    },
4878                ],
4879                "children": [
4880                    {
4881                        "name": "modular",
4882                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
4883                    },
4884                ],
4885            }),
4886            Err(Error::Validate { err, .. }) if &err == "directory \"pkg_directory\" is offered from self, so it must be declared as a \"directory\" in \"capabilities\""
4887        ),
4888        test_cml_offer_runner_from_self_missing(
4889            json!({
4890                "offer": [
4891                    {
4892                        "runner": "dart",
4893                        "from": "self",
4894                        "to": [ "#modular" ],
4895                    },
4896                ],
4897                "children": [
4898                    {
4899                        "name": "modular",
4900                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
4901                    },
4902                ],
4903            }),
4904            Err(Error::Validate { err, .. }) if &err == "runner \"dart\" is offered from self, so it must be declared as a \"runner\" in \"capabilities\""
4905        ),
4906        test_cml_offer_resolver_from_self_missing(
4907            json!({
4908                "offer": [
4909                    {
4910                        "resolver": "pkg_resolver",
4911                        "from": "self",
4912                        "to": [ "#modular" ],
4913                    },
4914                ],
4915                "children": [
4916                    {
4917                        "name": "modular",
4918                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
4919                    },
4920                ],
4921            }),
4922            Err(Error::Validate { err, .. }) if &err == "resolver \"pkg_resolver\" is offered from self, so it must be declared as a \"resolver\" in \"capabilities\""
4923        ),
4924        test_cml_offer_storage_from_self_missing(
4925            json!({
4926                    "offer": [
4927                        {
4928                            "storage": "cache",
4929                            "from": "self",
4930                            "to": [ "#echo_server" ],
4931                        },
4932                    ],
4933                    "children": [
4934                        {
4935                            "name": "echo_server",
4936                            "url": "fuchsia-pkg://fuchsia.com/echo_server#meta/echo_server.cm",
4937                        },
4938                    ],
4939                }),
4940            Err(Error::Validate { err, .. }) if &err == "storage \"cache\" is offered from self, so it must be declared as a \"storage\" in \"capabilities\""
4941        ),
4942        test_cml_offer_from_self_missing_dictionary(
4943            json!({
4944                "offer": [
4945                    {
4946                        "protocol": "foo_protocol",
4947                        "from": "self/dict/inner",
4948                        "to": [ "#modular" ],
4949                    },
4950                ],
4951                "children": [
4952                    {
4953                        "name": "modular",
4954                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
4955                    },
4956                ],
4957            }),
4958            Err(Error::Validate { err, .. }) if &err == "protocol \"foo_protocol\" is offered from \"self/dict/inner\", so \"dict\" must be declared as a \"dictionary\" in \"capabilities\""
4959        ),
4960        test_cml_offer_dependency_on_wrong_type(
4961            json!({
4962                    "offer": [ {
4963                        "resolver": "fuchsia.logger.Log",
4964                        "from": "parent",
4965                        "to": [ "#echo_server" ],
4966                        "dependency": "strong",
4967                    } ],
4968                    "children": [ {
4969                        "name": "echo_server",
4970                        "url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo_server.cm",
4971                    } ],
4972                }),
4973            Err(Error::Validate { err, .. }) if err.starts_with("Dependency can only be provided for")
4974        ),
4975        test_cml_offer_dependency_cycle(
4976            json!({
4977                    "offer": [
4978                        {
4979                            "protocol": "fuchsia.logger.Log",
4980                            "from": "#a",
4981                            "to": [ "#b" ],
4982                            "dependency": "strong"
4983                        },
4984                        {
4985                            "directory": "data",
4986                            "from": "#b",
4987                            "to": [ "#c" ],
4988                        },
4989                        {
4990                            "protocol": "ethernet",
4991                            "from": "#c",
4992                            "to": [ "#a" ],
4993                        },
4994                        {
4995                            "runner": "elf",
4996                            "from": "#b",
4997                            "to": [ "#d" ],
4998                        },
4999                        {
5000                            "resolver": "http",
5001                            "from": "#d",
5002                            "to": [ "#b" ],
5003                        },
5004                    ],
5005                    "children": [
5006                        {
5007                            "name": "a",
5008                            "url": "fuchsia-pkg://fuchsia.com/a#meta/a.cm"
5009                        },
5010                        {
5011                            "name": "b",
5012                            "url": "fuchsia-pkg://fuchsia.com/b#meta/b.cm"
5013                        },
5014                        {
5015                            "name": "c",
5016                            "url": "fuchsia-pkg://fuchsia.com/b#meta/c.cm"
5017                        },
5018                        {
5019                            "name": "d",
5020                            "url": "fuchsia-pkg://fuchsia.com/b#meta/d.cm"
5021                        },
5022                    ]
5023                }),
5024            Err(Error::Validate {
5025                err,
5026                ..
5027            }) if &err ==
5028                "Strong dependency cycles were found. Break the cycle by removing a \
5029                dependency or marking an offer as weak. Cycles: \
5030                {{#a -> #b -> #c -> #a}, {#b -> #d -> #b}}"
5031        ),
5032        test_cml_offer_dependency_cycle_storage(
5033            json!({
5034                "capabilities": [
5035                    {
5036                        "storage": "data",
5037                        "from": "#backend",
5038                        "backing_dir": "blobfs",
5039                        "storage_id": "static_instance_id_or_moniker",
5040                    },
5041                ],
5042                "children": [
5043                    {
5044                        "name": "child",
5045                        "url": "#meta/child.cm",
5046                    },
5047                    {
5048                        "name": "backend",
5049                        "url": "#meta/backend.cm",
5050                    },
5051                ],
5052                "offer": [
5053                    {
5054                        "protocol": "fuchsia.example.Protocol",
5055                        "from": "#child",
5056                        "to": "#backend",
5057                    },
5058                    {
5059                        "storage": "data",
5060                        "from": "self",
5061                        "to": "#child",
5062                    },
5063                ],
5064            }),
5065            Err(Error::Validate {
5066                err,
5067                ..
5068            }) if &err ==
5069                "Strong dependency cycles were found. Break the cycle by removing a \
5070                dependency or marking an offer as weak. Cycles: \
5071                {{#backend -> #data -> #child -> #backend}}"
5072        ),
5073        test_cml_offer_weak_dependency_cycle(
5074            json!({
5075                    "offer": [
5076                        {
5077                            "protocol": "fuchsia.logger.Log",
5078                            "from": "#child_a",
5079                            "to": [ "#child_b" ],
5080                            "dependency": "weak"
5081                        },
5082                        {
5083                            "service": "fuchsia.service.Test",
5084                            "from": "#child_a",
5085                            "to": [ "#child_b" ],
5086                            "dependency": "weak"
5087                        },
5088                        {
5089                            "directory": "data",
5090                            "from": "#child_b",
5091                            "to": [ "#child_a" ],
5092                        },
5093                    ],
5094                    "children": [
5095                        {
5096                            "name": "child_a",
5097                            "url": "fuchsia-pkg://fuchsia.com/child_a#meta/child_a.cm"
5098                        },
5099                        {
5100                            "name": "child_b",
5101                            "url": "fuchsia-pkg://fuchsia.com/child_b#meta/child_b.cm"
5102                        },
5103                    ]
5104                }),
5105            Ok(())
5106        ),
5107
5108        // children
5109        test_cml_children(
5110            json!({
5111                "children": [
5112                    {
5113                        "name": "logger",
5114                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
5115                        "on_terminate": "reboot",
5116                    },
5117                    {
5118                        "name": "gmail",
5119                        "url": "https://www.google.com/gmail",
5120                        "startup": "eager",
5121                    },
5122                ]
5123            }),
5124            Ok(())
5125        ),
5126        test_cml_children_missing_props(
5127            json!({
5128                "children": [ {} ]
5129            }),
5130            Err(Error::Parse { err, .. }) if &err == "missing field `name`"
5131        ),
5132        test_cml_children_duplicate_names(
5133            json!({
5134                "children": [
5135                     {
5136                         "name": "logger",
5137                         "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
5138                     },
5139                     {
5140                         "name": "logger",
5141                         "url": "fuchsia-pkg://fuchsia.com/logger/beta#meta/logger.cm"
5142                     }
5143                 ]
5144             }),
5145             Err(Error::Validate { err, .. }) if &err == "identifier \"logger\" is defined twice, once in \"children\" and once in \"children\""
5146         ),
5147         test_cml_children_url_ends_in_cml(
5148            json!({
5149                "children": [
5150                     {
5151                         "name": "logger",
5152                         "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cml"
5153                     },
5154                 ]
5155             }),
5156             Err(Error::Validate { err, ..}) if &err == "child URL ends in .cml instead of .cm, which is almost certainly a mistake: fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cml"
5157         ),
5158          test_cml_children_bad_startup(
5159            json!({
5160                "children": [
5161                    {
5162                        "name": "logger",
5163                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
5164                        "startup": "zzz",
5165                    },
5166                ],
5167            }),
5168            Err(Error::Parse { err, .. }) if &err == "unknown variant `zzz`, expected `lazy` or `eager`"
5169        ),
5170        test_cml_children_bad_on_terminate(
5171            json!({
5172                "children": [
5173                    {
5174                        "name": "logger",
5175                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
5176                        "on_terminate": "zzz",
5177                    },
5178                ],
5179            }),
5180            Err(Error::Parse { err, .. }) if &err == "unknown variant `zzz`, expected `none` or `reboot`"
5181        ),
5182
5183
5184        test_cml_children_bad_environment(
5185            json!({
5186                "children": [
5187                    {
5188                        "name": "logger",
5189                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
5190                        "environment": "parent",
5191                    }
5192                ]
5193            }),
5194            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"parent\", expected \"#<environment-name>\""
5195        ),
5196        test_cml_children_unknown_environment(
5197            json!({
5198                "children": [
5199                    {
5200                        "name": "logger",
5201                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
5202                        "environment": "#foo_env",
5203                    }
5204                ]
5205            }),
5206            Err(Error::Validate { err, .. }) if &err == "\"foo_env\" does not appear in \"environments\""
5207        ),
5208        test_cml_children_environment(
5209            json!({
5210                "children": [
5211                    {
5212                        "name": "logger",
5213                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
5214                        "environment": "#foo_env",
5215                    }
5216                ],
5217                "environments": [
5218                    {
5219                        "name": "foo_env",
5220                    }
5221                ]
5222            }),
5223            Ok(())
5224        ),
5225        test_cml_collections_bad_environment(
5226            json!({
5227                "collections": [
5228                    {
5229                        "name": "tests",
5230                        "durability": "transient",
5231                        "environment": "parent",
5232                    }
5233                ]
5234            }),
5235            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"parent\", expected \"#<environment-name>\""
5236        ),
5237        test_cml_collections_unknown_environment(
5238            json!({
5239                "collections": [
5240                    {
5241                        "name": "tests",
5242                        "durability": "transient",
5243                        "environment": "#foo_env",
5244                    }
5245                ]
5246            }),
5247            Err(Error::Validate { err, .. }) if &err == "\"foo_env\" does not appear in \"environments\""
5248        ),
5249        test_cml_collections_environment(
5250            json!({
5251                "collections": [
5252                    {
5253                        "name": "tests",
5254                        "durability": "transient",
5255                        "environment": "#foo_env",
5256                    }
5257                ],
5258                "environments": [
5259                    {
5260                        "name": "foo_env",
5261                    }
5262                ]
5263            }),
5264            Ok(())
5265        ),
5266
5267        test_cml_environment_timeout(
5268            json!({
5269                "environments": [
5270                    {
5271                        "name": "foo_env",
5272                        "__stop_timeout_ms": 10000,
5273                    }
5274                ]
5275            }),
5276            Ok(())
5277        ),
5278
5279        test_cml_environment_bad_timeout(
5280            json!({
5281                "environments": [
5282                    {
5283                        "name": "foo_env",
5284                        "__stop_timeout_ms": -3,
5285                    }
5286                ]
5287            }),
5288            Err(Error::Parse { err, .. }) if &err == "invalid value: integer `-3`, expected an unsigned 32-bit integer"
5289        ),
5290        test_cml_environment_debug(
5291            json!({
5292                "capabilities": [
5293                    {
5294                        "protocol": "fuchsia.logger.Log2",
5295                    },
5296                ],
5297                "environments": [
5298                    {
5299                        "name": "foo_env",
5300                        "extends": "realm",
5301                        "debug": [
5302                            {
5303                                "protocol": "fuchsia.module.Module",
5304                                "from": "#modular",
5305                            },
5306                            {
5307                                "protocol": "fuchsia.logger.OtherLog",
5308                                "from": "parent",
5309                            },
5310                            {
5311                                "protocol": "fuchsia.logger.Log2",
5312                                "from": "self",
5313                            },
5314                        ]
5315                    }
5316                ],
5317                "children": [
5318                    {
5319                        "name": "modular",
5320                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
5321                    },
5322                ],
5323            }),
5324           Ok(())
5325        ),
5326        test_cml_environment_debug_missing_capability(
5327            json!({
5328                "environments": [
5329                    {
5330                        "name": "foo_env",
5331                        "extends": "realm",
5332                        "debug": [
5333                            {
5334                                "protocol": "fuchsia.module.Module",
5335                                "from": "#modular",
5336                            },
5337                            {
5338                                "protocol": "fuchsia.logger.OtherLog",
5339                                "from": "parent",
5340                            },
5341                            {
5342                                "protocol": "fuchsia.logger.Log2",
5343                                "from": "self",
5344                            },
5345                        ]
5346                    }
5347                ],
5348                "children": [
5349                    {
5350                        "name": "modular",
5351                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
5352                    },
5353                ],
5354            }),
5355            Err(Error::Validate { err, .. }) if &err == "protocol \"fuchsia.logger.Log2\" is registered as debug from self, so it must be declared as a \"protocol\" in \"capabilities\""
5356        ),
5357        test_cml_environment_invalid_from_child(
5358            json!({
5359                "capabilities": [
5360                    {
5361                        "protocol": "fuchsia.logger.Log2",
5362                    },
5363                ],
5364                "environments": [
5365                    {
5366                        "name": "foo_env",
5367                        "extends": "realm",
5368                        "debug": [
5369                            {
5370                                "protocol": "fuchsia.module.Module",
5371                                "from": "#missing",
5372                            },
5373                            {
5374                                "protocol": "fuchsia.logger.OtherLog",
5375                                "from": "parent",
5376                            },
5377                            {
5378                                "protocol": "fuchsia.logger.Log2",
5379                                "from": "self",
5380                            },
5381                        ]
5382                    }
5383                ],
5384                "children": [
5385                    {
5386                        "name": "modular",
5387                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
5388                    },
5389                ],
5390            }),
5391            Err(Error::Validate { err, .. }) if &err == "\"debug\" source \"#missing\" does not appear in \"children\" or \"capabilities\""
5392        ),
5393
5394
5395        // collections
5396        test_cml_collections(
5397            json!({
5398                "collections": [
5399                    {
5400                        "name": "test_single_run_coll",
5401                        "durability": "single_run"
5402                    },
5403                    {
5404                        "name": "test_transient_coll",
5405                        "durability": "transient"
5406                    },
5407                ]
5408            }),
5409            Ok(())
5410        ),
5411        test_cml_collections_missing_props(
5412            json!({
5413                "collections": [ {} ]
5414            }),
5415            Err(Error::Parse { err, .. }) if &err == "missing field `name`"
5416        ),
5417        test_cml_collections_duplicate_names(
5418           json!({
5419               "collections": [
5420                    {
5421                        "name": "duplicate",
5422                        "durability": "single_run"
5423                    },
5424                    {
5425                        "name": "duplicate",
5426                        "durability": "transient"
5427                    }
5428                ]
5429            }),
5430            Err(Error::Validate { err, .. }) if &err == "identifier \"duplicate\" is defined twice, once in \"collections\" and once in \"collections\""
5431        ),
5432        test_cml_collections_bad_durability(
5433            json!({
5434                "collections": [
5435                    {
5436                        "name": "modular",
5437                        "durability": "zzz",
5438                    },
5439                ],
5440            }),
5441            Err(Error::Parse { err, .. }) if &err == "unknown variant `zzz`, expected `transient` or `single_run`"
5442        ),
5443
5444        // capabilities
5445        test_cml_protocol(
5446            json!({
5447                "capabilities": [
5448                    {
5449                        "protocol": "a",
5450                        "path": "/minfs",
5451                    },
5452                    {
5453                        "protocol": "b",
5454                        "path": "/data",
5455                    },
5456                    {
5457                        "protocol": "c",
5458                    },
5459                ],
5460            }),
5461            Ok(())
5462        ),
5463        test_cml_protocol_multi(
5464            json!({
5465                "capabilities": [
5466                    {
5467                        "protocol": ["a", "b", "c"],
5468                    },
5469                ],
5470            }),
5471            Ok(())
5472        ),
5473        test_cml_protocol_multi_invalid_path(
5474            json!({
5475                "capabilities": [
5476                    {
5477                        "protocol": ["a", "b", "c"],
5478                        "path": "/minfs",
5479                    },
5480                ],
5481            }),
5482            Err(Error::Validate { err, .. }) if &err == "\"path\" can only be specified when one `protocol` is supplied."
5483        ),
5484        test_cml_protocol_all_valid_chars(
5485            json!({
5486                "capabilities": [
5487                    {
5488                        "protocol": "abcdefghijklmnopqrstuvwxyz0123456789_-service",
5489                    },
5490                ],
5491            }),
5492            Ok(())
5493        ),
5494        test_cml_directory(
5495            json!({
5496                "capabilities": [
5497                    {
5498                        "directory": "a",
5499                        "path": "/minfs",
5500                        "rights": ["connect"],
5501                    },
5502                    {
5503                        "directory": "b",
5504                        "path": "/data",
5505                        "rights": ["connect"],
5506                    },
5507                ],
5508            }),
5509            Ok(())
5510        ),
5511        test_cml_directory_all_valid_chars(
5512            json!({
5513                "capabilities": [
5514                    {
5515                        "directory": "abcdefghijklmnopqrstuvwxyz0123456789_-service",
5516                        "path": "/data",
5517                        "rights": ["connect"],
5518                    },
5519                ],
5520            }),
5521            Ok(())
5522        ),
5523        test_cml_directory_missing_path(
5524            json!({
5525                "capabilities": [
5526                    {
5527                        "directory": "dir",
5528                        "rights": ["connect"],
5529                    },
5530                ]
5531            }),
5532            Err(Error::Validate { err, .. }) if &err == "\"path\" should be present with \"directory\""
5533        ),
5534        test_cml_directory_missing_rights(
5535            json!({
5536                "capabilities": [
5537                    {
5538                        "directory": "dir",
5539                        "path": "/dir",
5540                    },
5541                ]
5542            }),
5543            Err(Error::Validate { err, .. }) if &err == "\"rights\" should be present with \"directory\""
5544        ),
5545        test_cml_storage(
5546            json!({
5547                "capabilities": [
5548                    {
5549                        "storage": "a",
5550                        "from": "#minfs",
5551                        "backing_dir": "minfs",
5552                        "storage_id": "static_instance_id",
5553                    },
5554                    {
5555                        "storage": "b",
5556                        "from": "parent",
5557                        "backing_dir": "data",
5558                        "storage_id": "static_instance_id_or_moniker",
5559                    },
5560                    {
5561                        "storage": "c",
5562                        "from": "self",
5563                        "backing_dir": "storage",
5564                        "storage_id": "static_instance_id_or_moniker",
5565                    },
5566                ],
5567                "children": [
5568                    {
5569                        "name": "minfs",
5570                        "url": "fuchsia-pkg://fuchsia.com/minfs/stable#meta/minfs.cm",
5571                    },
5572                ],
5573            }),
5574            Ok(())
5575        ),
5576        test_cml_storage_all_valid_chars(
5577            json!({
5578                "capabilities": [
5579                    {
5580                        "storage": "abcdefghijklmnopqrstuvwxyz0123456789_-storage",
5581                        "from": "#abcdefghijklmnopqrstuvwxyz0123456789_-from",
5582                        "backing_dir": "example",
5583                        "storage_id": "static_instance_id_or_moniker",
5584                    },
5585                ],
5586                "children": [
5587                    {
5588                        "name": "abcdefghijklmnopqrstuvwxyz0123456789_-from",
5589                        "url": "https://www.google.com/gmail",
5590                    },
5591                ],
5592            }),
5593            Ok(())
5594        ),
5595        test_cml_storage_invalid_from(
5596            json!({
5597                    "capabilities": [ {
5598                        "storage": "minfs",
5599                        "from": "#missing",
5600                        "backing_dir": "minfs",
5601                        "storage_id": "static_instance_id_or_moniker",
5602                    } ]
5603                }),
5604            Err(Error::Validate { err, .. }) if &err == "\"capabilities\" source \"#missing\" does not appear in \"children\""
5605        ),
5606        test_cml_storage_missing_path_or_backing_dir(
5607            json!({
5608                    "capabilities": [ {
5609                        "storage": "minfs",
5610                        "from": "self",
5611                        "storage_id": "static_instance_id_or_moniker",
5612                    } ]
5613                }),
5614            Err(Error::Validate { err, .. }) if &err == "\"backing_dir\" should be present with \"storage\""
5615
5616        ),
5617        test_cml_storage_missing_storage_id(
5618            json!({
5619                    "capabilities": [ {
5620                        "storage": "minfs",
5621                        "from": "self",
5622                        "backing_dir": "storage",
5623                    }, ]
5624                }),
5625            Err(Error::Validate { err, .. }) if &err == "\"storage_id\" should be present with \"storage\""
5626        ),
5627        test_cml_storage_path(
5628            json!({
5629                    "capabilities": [ {
5630                        "storage": "minfs",
5631                        "from": "self",
5632                        "path": "/minfs",
5633                        "storage_id": "static_instance_id_or_moniker",
5634                    } ]
5635                }),
5636            Err(Error::Validate { err, .. }) if &err == "\"path\" can not be present with \"storage\", use \"backing_dir\""
5637        ),
5638        test_cml_runner(
5639            json!({
5640                "capabilities": [
5641                    {
5642                        "runner": "a",
5643                        "path": "/minfs",
5644                    },
5645                ],
5646            }),
5647            Ok(())
5648        ),
5649        test_cml_runner_all_valid_chars(
5650            json!({
5651                "children": [
5652                    {
5653                        "name": "abcdefghijklmnopqrstuvwxyz0123456789_-from",
5654                        "url": "https://www.google.com/gmail"
5655                    },
5656                ],
5657                "capabilities": [
5658                    {
5659                        "runner": "abcdefghijklmnopqrstuvwxyz0123456789_-runner",
5660                        "path": "/example",
5661                    },
5662                ]
5663            }),
5664            Ok(())
5665        ),
5666        test_cml_runner_extraneous_from(
5667            json!({
5668                "capabilities": [
5669                    {
5670                        "runner": "a",
5671                        "path": "/example",
5672                        "from": "self",
5673                    },
5674                ]
5675            }),
5676            Err(Error::Validate { err, .. }) if &err == "\"from\" should not be present with \"runner\""
5677        ),
5678        test_cml_capability_missing_name(
5679            json!({
5680                "capabilities": [
5681                    {
5682                        "path": "/svc/fuchsia.component.resolution.Resolver",
5683                    },
5684                ]
5685            }),
5686            Err(Error::Validate { err, .. }) if &err == "`capability` declaration is missing a capability keyword, one of: \"service\", \"protocol\", \"directory\", \"storage\", \"runner\", \"resolver\", \"event_stream\", \"dictionary\", \"config\""
5687        ),
5688        test_cml_resolver_missing_path(
5689            json!({
5690                "capabilities": [
5691                    {
5692                        "resolver": "pkg_resolver",
5693                    },
5694                ]
5695            }),
5696            Err(Error::Validate { err, .. }) if &err == "\"path\" should be present with \"resolver\""
5697        ),
5698        test_cml_capabilities_extraneous_from(
5699            json!({
5700                "capabilities": [
5701                    {
5702                        "resolver": "pkg_resolver",
5703                        "path": "/svc/fuchsia.component.resolution.Resolver",
5704                        "from": "self",
5705                    },
5706                ]
5707            }),
5708            Err(Error::Validate { err, .. }) if &err == "\"from\" should not be present with \"resolver\""
5709        ),
5710        test_cml_capabilities_duplicates(
5711            json!({
5712                "capabilities": [
5713                    {
5714                        "runner": "pkg_resolver",
5715                        "path": "/svc/fuchsia.component.resolution.Resolver",
5716                    },
5717                    {
5718                        "resolver": "pkg_resolver",
5719                        "path": "/svc/my-resolver",
5720                    },
5721                ]
5722            }),
5723            Err(Error::Validate { err, .. }) if &err == "identifier \"pkg_resolver\" is defined twice, once in \"resolvers\" and once in \"runners\""
5724        ),
5725
5726        // environments
5727        test_cml_environments(
5728            json!({
5729                "environments": [
5730                    {
5731                        "name": "my_env_a",
5732                    },
5733                    {
5734                        "name": "my_env_b",
5735                        "extends": "realm",
5736                    },
5737                    {
5738                        "name": "my_env_c",
5739                        "extends": "none",
5740                        "__stop_timeout_ms": 8000,
5741                    },
5742                ],
5743            }),
5744            Ok(())
5745        ),
5746
5747        test_invalid_cml_environment_no_stop_timeout(
5748            json!({
5749                "environments": [
5750                    {
5751                        "name": "my_env",
5752                        "extends": "none",
5753                    },
5754                ],
5755            }),
5756            Err(Error::Validate { err, .. }) if &err ==
5757                "'__stop_timeout_ms' must be provided if the environment does not extend \
5758                another environment"
5759        ),
5760
5761        test_cml_environment_invalid_extends(
5762            json!({
5763                "environments": [
5764                    {
5765                        "name": "my_env",
5766                        "extends": "some_made_up_string",
5767                    },
5768                ],
5769            }),
5770            Err(Error::Parse { err, .. }) if &err == "unknown variant `some_made_up_string`, expected `realm` or `none`"
5771        ),
5772        test_cml_environment_missing_props(
5773            json!({
5774                "environments": [ {} ]
5775            }),
5776            Err(Error::Parse { err, .. }) if &err == "missing field `name`"
5777        ),
5778
5779        test_cml_environment_with_runners(
5780            json!({
5781                "environments": [
5782                    {
5783                        "name": "my_env",
5784                        "extends": "realm",
5785                        "runners": [
5786                            {
5787                                "runner": "dart",
5788                                "from": "parent",
5789                            }
5790                        ]
5791                    }
5792                ],
5793            }),
5794            Ok(())
5795        ),
5796        test_cml_environment_with_runners_alias(
5797            json!({
5798                "environments": [
5799                    {
5800                        "name": "my_env",
5801                        "extends": "realm",
5802                        "runners": [
5803                            {
5804                                "runner": "dart",
5805                                "from": "parent",
5806                                "as": "my-dart",
5807                            }
5808                        ]
5809                    }
5810                ],
5811            }),
5812            Ok(())
5813        ),
5814        test_cml_environment_with_runners_missing(
5815            json!({
5816                "environments": [
5817                    {
5818                        "name": "my_env",
5819                        "extends": "realm",
5820                        "runners": [
5821                            {
5822                                "runner": "dart",
5823                                "from": "self",
5824                            }
5825                        ]
5826                    }
5827                ],
5828                "capabilities": [
5829                     {
5830                         "runner": "dart",
5831                         "path": "/svc/fuchsia.component.Runner",
5832                     }
5833                ],
5834            }),
5835            Ok(())
5836        ),
5837        test_cml_environment_with_runners_bad_name(
5838            json!({
5839                "environments": [
5840                    {
5841                        "name": "my_env",
5842                        "extends": "realm",
5843                        "runners": [
5844                            {
5845                                "runner": "elf",
5846                                "from": "parent",
5847                                "as": "#elf",
5848                            }
5849                        ]
5850                    }
5851                ],
5852            }),
5853            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"#elf\", expected a \
5854            name that consists of [A-Za-z0-9_.-] and starts with [A-Za-z0-9_]"
5855        ),
5856        test_cml_environment_with_runners_duplicate_name(
5857            json!({
5858                "environments": [
5859                    {
5860                        "name": "my_env",
5861                        "extends": "realm",
5862                        "runners": [
5863                            {
5864                                "runner": "dart",
5865                                "from": "parent",
5866                            },
5867                            {
5868                                "runner": "other-dart",
5869                                "from": "parent",
5870                                "as": "dart",
5871                            }
5872                        ]
5873                    }
5874                ],
5875            }),
5876            Err(Error::Validate { err, .. }) if &err == "Duplicate runners registered under name \"dart\": \"other-dart\" and \"dart\"."
5877        ),
5878        test_cml_environment_with_runner_from_missing_child(
5879            json!({
5880                "environments": [
5881                    {
5882                        "name": "my_env",
5883                        "extends": "realm",
5884                        "runners": [
5885                            {
5886                                "runner": "elf",
5887                                "from": "#missing_child",
5888                            }
5889                        ]
5890                    }
5891                ]
5892            }),
5893            Err(Error::Validate { err, .. }) if &err == "\"elf\" runner source \"#missing_child\" does not appear in \"children\""
5894        ),
5895        test_cml_environment_with_runner_cycle(
5896            json!({
5897                "environments": [
5898                    {
5899                        "name": "my_env",
5900                        "extends": "realm",
5901                        "runners": [
5902                            {
5903                                "runner": "elf",
5904                                "from": "#child",
5905                                "as": "my-elf",
5906                            }
5907                        ]
5908                    }
5909                ],
5910                "children": [
5911                    {
5912                        "name": "child",
5913                        "url": "fuchsia-pkg://child",
5914                        "environment": "#my_env",
5915                    }
5916                ]
5917            }),
5918            Err(Error::Validate { err, .. }) if &err ==
5919                    "Strong dependency cycles were found. Break the cycle by removing a \
5920                    dependency or marking an offer as weak. Cycles: \
5921                    {{#child -> #my_env -> #child}}"
5922        ),
5923        test_cml_environment_with_resolvers(
5924            json!({
5925                "environments": [
5926                    {
5927                        "name": "my_env",
5928                        "extends": "realm",
5929                        "resolvers": [
5930                            {
5931                                "resolver": "pkg_resolver",
5932                                "from": "parent",
5933                                "scheme": "fuchsia-pkg",
5934                            }
5935                        ]
5936                    }
5937                ],
5938            }),
5939            Ok(())
5940        ),
5941        test_cml_environment_with_resolvers_bad_scheme(
5942            json!({
5943                "environments": [
5944                    {
5945                        "name": "my_env",
5946                        "extends": "realm",
5947                        "resolvers": [
5948                            {
5949                                "resolver": "pkg_resolver",
5950                                "from": "parent",
5951                                "scheme": "9scheme",
5952                            }
5953                        ]
5954                    }
5955                ],
5956            }),
5957            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"9scheme\", expected a valid URL scheme"
5958        ),
5959        test_cml_environment_with_resolvers_duplicate_scheme(
5960            json!({
5961                "environments": [
5962                    {
5963                        "name": "my_env",
5964                        "extends": "realm",
5965                        "resolvers": [
5966                            {
5967                                "resolver": "pkg_resolver",
5968                                "from": "parent",
5969                                "scheme": "fuchsia-pkg",
5970                            },
5971                            {
5972                                "resolver": "base_resolver",
5973                                "from": "parent",
5974                                "scheme": "fuchsia-pkg",
5975                            }
5976                        ]
5977                    }
5978                ],
5979            }),
5980            Err(Error::Validate { err, .. }) if &err == "scheme \"fuchsia-pkg\" for resolver \"base_resolver\" is already registered; previously registered to resolver \"pkg_resolver\"."
5981        ),
5982        test_cml_environment_with_resolver_from_missing_child(
5983            json!({
5984                "environments": [
5985                    {
5986                        "name": "my_env",
5987                        "extends": "realm",
5988                        "resolvers": [
5989                            {
5990                                "resolver": "pkg_resolver",
5991                                "from": "#missing_child",
5992                                "scheme": "fuchsia-pkg",
5993                            }
5994                        ]
5995                    }
5996                ]
5997            }),
5998            Err(Error::Validate { err, .. }) if &err == "\"pkg_resolver\" resolver source \"#missing_child\" does not appear in \"children\""
5999        ),
6000        test_cml_environment_with_resolver_cycle(
6001            json!({
6002                "environments": [
6003                    {
6004                        "name": "my_env",
6005                        "extends": "realm",
6006                        "resolvers": [
6007                            {
6008                                "resolver": "pkg_resolver",
6009                                "from": "#child",
6010                                "scheme": "fuchsia-pkg",
6011                            }
6012                        ]
6013                    }
6014                ],
6015                "children": [
6016                    {
6017                        "name": "child",
6018                        "url": "fuchsia-pkg://child",
6019                        "environment": "#my_env",
6020                    }
6021                ]
6022            }),
6023            Err(Error::Validate { err, .. }) if &err ==
6024                    "Strong dependency cycles were found. Break the cycle by removing a \
6025                    dependency or marking an offer as weak. \
6026                    Cycles: {{#child -> #my_env -> #child}}"
6027        ),
6028        test_cml_environment_with_cycle_multiple_components(
6029            json!({
6030                "environments": [
6031                    {
6032                        "name": "my_env",
6033                        "extends": "realm",
6034                        "resolvers": [
6035                            {
6036                                "resolver": "pkg_resolver",
6037                                "from": "#b",
6038                                "scheme": "fuchsia-pkg",
6039                            }
6040                        ]
6041                    }
6042                ],
6043                "children": [
6044                    {
6045                        "name": "a",
6046                        "url": "fuchsia-pkg://a",
6047                        "environment": "#my_env",
6048                    },
6049                    {
6050                        "name": "b",
6051                        "url": "fuchsia-pkg://b",
6052                    }
6053                ],
6054                "offer": [
6055                    {
6056                        "protocol": "fuchsia.logger.Log",
6057                        "from": "#a",
6058                        "to": [ "#b" ],
6059                        "dependency": "strong"
6060                    },
6061                ]
6062            }),
6063            Err(Error::Validate { err, .. }) if &err ==
6064                "Strong dependency cycles were found. Break the cycle by removing a dependency \
6065                or marking an offer as weak. \
6066                Cycles: {{#a -> #b -> #my_env -> #a}}"
6067        ),
6068
6069        // facets
6070        test_cml_facets(
6071            json!({
6072                "facets": {
6073                    "metadata": {
6074                        "title": "foo",
6075                        "authors": [ "me", "you" ],
6076                        "year": 2018
6077                    }
6078                }
6079            }),
6080            Ok(())
6081        ),
6082        test_cml_facets_wrong_type(
6083            json!({
6084                "facets": 55
6085            }),
6086            Err(Error::Parse { err, .. }) if &err == "invalid type: integer `55`, expected a map"
6087        ),
6088
6089        // constraints
6090        test_cml_rights_all(
6091            json!({
6092                "use": [
6093                  {
6094                    "directory": "mydir",
6095                    "path": "/mydir",
6096                    "rights": ["connect", "enumerate", "read_bytes", "write_bytes",
6097                               "execute", "update_attributes", "get_attributes", "traverse",
6098                               "modify_directory"],
6099                  },
6100                ]
6101            }),
6102            Ok(())
6103        ),
6104        test_cml_rights_invalid(
6105            json!({
6106                "use": [
6107                  {
6108                    "directory": "mydir",
6109                    "path": "/mydir",
6110                    "rights": ["cAnnect", "enumerate"],
6111                  },
6112                ]
6113            }),
6114            Err(Error::Parse { err, .. }) if &err == "unknown variant `cAnnect`, expected one of `connect`, `enumerate`, `execute`, `get_attributes`, `modify_directory`, `read_bytes`, `traverse`, `update_attributes`, `write_bytes`, `r*`, `w*`, `x*`, `rw*`, `rx*`"
6115        ),
6116        test_cml_rights_duplicate(
6117            json!({
6118                "use": [
6119                  {
6120                    "directory": "mydir",
6121                    "path": "/mydir",
6122                    "rights": ["connect", "connect"],
6123                  },
6124                ]
6125            }),
6126            Err(Error::Parse { err, .. }) if &err == "invalid value: array with duplicate element, expected a nonempty array of rights, with unique elements"
6127        ),
6128        test_cml_rights_empty(
6129            json!({
6130                "use": [
6131                  {
6132                    "directory": "mydir",
6133                    "path": "/mydir",
6134                    "rights": [],
6135                  },
6136                ]
6137            }),
6138            Err(Error::Parse { err, .. }) if &err == "invalid length 0, expected a nonempty array of rights, with unique elements"
6139        ),
6140        test_cml_rights_alias_star_expansion(
6141            json!({
6142                "use": [
6143                  {
6144                    "directory": "mydir",
6145                    "rights": ["r*"],
6146                    "path": "/mydir",
6147                  },
6148                ]
6149            }),
6150            Ok(())
6151        ),
6152        test_cml_rights_alias_star_expansion_with_longform(
6153            json!({
6154                "use": [
6155                  {
6156                    "directory": "mydir",
6157                    "rights": ["w*", "read_bytes"],
6158                    "path": "/mydir",
6159                  },
6160                ]
6161            }),
6162            Ok(())
6163        ),
6164        test_cml_rights_alias_star_expansion_with_longform_collision(
6165            json!({
6166                "use": [
6167                  {
6168                    "directory": "mydir",
6169                    "path": "/mydir",
6170                    "rights": ["r*", "read_bytes"],
6171                  },
6172                ]
6173            }),
6174            Err(Error::Validate { err, .. }) if &err == "\"read_bytes\" is duplicated in the rights clause."
6175        ),
6176        test_cml_rights_alias_star_expansion_collision(
6177            json!({
6178                "use": [
6179                  {
6180                    "directory": "mydir",
6181                    "path": "/mydir",
6182                    "rights": ["w*", "x*"],
6183                  },
6184                ]
6185            }),
6186            Err(Error::Validate { err, .. }) if &err == "\"x*\" is duplicated in the rights clause."
6187        ),
6188        test_cml_rights_use_invalid(
6189            json!({
6190                "use": [
6191                  { "directory": "mydir", "path": "/mydir" },
6192                ]
6193            }),
6194            Err(Error::Validate { err, .. }) if &err == "This use statement requires a `rights` field. Refer to: https://fuchsia.dev/go/components/directory#consumer."
6195        ),
6196
6197        test_cml_path(
6198            json!({
6199                "capabilities": [
6200                    {
6201                        "protocol": "foo",
6202                        "path": "/foo/in.-_/Bar",
6203                    },
6204                ]
6205            }),
6206            Ok(())
6207        ),
6208        test_cml_path_invalid_empty(
6209            json!({
6210                "capabilities": [
6211                    { "protocol": "foo", "path": "" },
6212                ]
6213            }),
6214            Err(Error::Parse { err, .. }) if &err == "invalid length 0, expected a non-empty path no more than fuchsia.io/MAX_PATH_LENGTH bytes in length"
6215        ),
6216        test_cml_path_invalid_root(
6217            json!({
6218                "capabilities": [
6219                    { "protocol": "foo", "path": "/" },
6220                ]
6221            }),
6222            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"/\", expected a path with leading `/` and non-empty segments, where each segment is no more than fuchsia.io/MAX_NAME_LENGTH bytes in length, cannot be . or .., and cannot contain embedded NULs"
6223        ),
6224        test_cml_path_invalid_absolute_is_relative(
6225            json!({
6226                "capabilities": [
6227                    { "protocol": "foo", "path": "foo/bar" },
6228                ]
6229            }),
6230            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"foo/bar\", expected a path with leading `/` and non-empty segments, where each segment is no more than fuchsia.io/MAX_NAME_LENGTH bytes in length, cannot be . or .., and cannot contain embedded NULs"
6231        ),
6232        test_cml_path_invalid_trailing(
6233            json!({
6234                "capabilities": [
6235                    { "protocol": "foo", "path":"/foo/bar/" },
6236                ]
6237            }),
6238            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"/foo/bar/\", expected a path with leading `/` and non-empty segments, where each segment is no more than fuchsia.io/MAX_NAME_LENGTH bytes in length, cannot be . or .., and cannot contain embedded NULs"
6239        ),
6240        test_cml_path_too_long(
6241            json!({
6242                "capabilities": [
6243                    { "protocol": "foo", "path": format!("/{}", "a".repeat(4095)) },
6244                ]
6245            }),
6246            Err(Error::Parse { err, .. }) if &err == "invalid length 4096, expected a non-empty path no more than fuchsia.io/MAX_PATH_LENGTH bytes in length"
6247        ),
6248        test_cml_path_invalid_segment(
6249            json!({
6250                "capabilities": [
6251                    { "protocol": "foo", "path": "/foo/../bar" },
6252                ]
6253            }),
6254            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"/foo/../bar\", expected a path with leading `/` and non-empty segments, where each segment is no more than fuchsia.io/MAX_NAME_LENGTH bytes in length, cannot be . or .., and cannot contain embedded NULs"
6255        ),
6256        test_cml_relative_path(
6257            json!({
6258                "use": [
6259                    {
6260                        "directory": "foo",
6261                        "path": "/foo",
6262                        "rights": ["r*"],
6263                        "subdir": "Baz/Bar",
6264                    },
6265                ]
6266            }),
6267            Ok(())
6268        ),
6269        test_cml_relative_path_invalid_empty(
6270            json!({
6271                "use": [
6272                    {
6273                        "directory": "foo",
6274                        "path": "/foo",
6275                        "rights": ["r*"],
6276                        "subdir": "",
6277                    },
6278                ]
6279            }),
6280            Err(Error::Parse { err, .. }) if &err == "invalid length 0, expected a non-empty path no more than fuchsia.io/MAX_PATH_LENGTH characters in length"
6281        ),
6282        test_cml_relative_path_invalid_root(
6283            json!({
6284                "use": [
6285                    {
6286                        "directory": "foo",
6287                        "path": "/foo",
6288                        "rights": ["r*"],
6289                        "subdir": "/",
6290                    },
6291                ]
6292            }),
6293            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"/\", expected a path with no leading `/` and non-empty segments"
6294        ),
6295        test_cml_relative_path_invalid_absolute(
6296            json!({
6297                "use": [
6298                    {
6299                        "directory": "foo",
6300                        "path": "/foo",
6301                        "rights": ["r*"],
6302                        "subdir": "/bar",
6303                    },
6304                ]
6305            }),
6306            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"/bar\", expected a path with no leading `/` and non-empty segments"
6307        ),
6308        test_cml_relative_path_invalid_trailing(
6309            json!({
6310                "use": [
6311                    {
6312                        "directory": "foo",
6313                        "path": "/foo",
6314                        "rights": ["r*"],
6315                        "subdir": "bar/",
6316                    },
6317                ]
6318            }),
6319            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"bar/\", expected a path with no leading `/` and non-empty segments"
6320        ),
6321        test_cml_relative_path_too_long(
6322            json!({
6323                "use": [
6324                    {
6325                        "directory": "foo",
6326                        "path": "/foo",
6327                        "rights": ["r*"],
6328                        "subdir": format!("{}", "a".repeat(4096)),
6329                    },
6330                ]
6331            }),
6332            Err(Error::Parse { err, .. }) if &err == "invalid length 4096, expected a non-empty path no more than fuchsia.io/MAX_PATH_LENGTH characters in length"
6333        ),
6334        test_cml_relative_ref_too_long(
6335            json!({
6336                "expose": [
6337                    {
6338                        "protocol": "fuchsia.logger.Log",
6339                        "from": &format!("#{}", "a".repeat(256)),
6340                    },
6341                ],
6342                "children": [
6343                    {
6344                        "name": "logger",
6345                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
6346                    },
6347                ]
6348            }),
6349            Err(Error::Parse { err, .. }) if &err == "invalid length 257, expected one or an array of \"framework\", \"self\", \"#<child-name>\", or a dictionary path"
6350        ),
6351        test_cml_dictionary_ref_invalid_root(
6352            json!({
6353                "use": [
6354                    {
6355                        "protocol": "a",
6356                        "from": "bad/a",
6357                    },
6358                ],
6359            }),
6360            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"bad/a\", expected \"parent\", \"framework\", \"debug\", \"self\", \"#<capability-name>\", \"#<child-name>\", \"#<collection-name>\", dictionary path, or none"
6361        ),
6362        test_cml_dictionary_ref_invalid_path(
6363            json!({
6364                "use": [
6365                    {
6366                        "protocol": "a",
6367                        "from": "parent//a",
6368                    },
6369                ],
6370            }),
6371            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"parent//a\", expected \"parent\", \"framework\", \"debug\", \"self\", \"#<capability-name>\", \"#<child-name>\", \"#<collection-name>\", dictionary path, or none"
6372        ),
6373        test_cml_dictionary_ref_too_long(
6374            json!({
6375                "use": [
6376                    {
6377                        "protocol": "a",
6378                        "from": format!("parent/{}", "a".repeat(4089)),
6379                    },
6380                ],
6381            }),
6382            Err(Error::Parse { err, .. }) if &err == "invalid length 4096, expected \"parent\", \"framework\", \"debug\", \"self\", \"#<capability-name>\", \"#<child-name>\", \"#<collection-name>\", dictionary path, or none"
6383        ),
6384        test_cml_capability_name(
6385            json!({
6386                "use": [
6387                    {
6388                        "protocol": "abcdefghijklmnopqrstuvwxyz0123456789_-.",
6389                    },
6390                ]
6391            }),
6392            Ok(())
6393        ),
6394        test_cml_capability_name_invalid(
6395            json!({
6396                "use": [
6397                    {
6398                        "protocol": "/bad",
6399                    },
6400                ]
6401            }),
6402            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"/bad\", expected a name or nonempty array of names, with unique elements"
6403        ),
6404        test_cml_child_name(
6405            json!({
6406                "children": [
6407                    {
6408                        "name": "abcdefghijklmnopqrstuvwxyz0123456789_-.",
6409                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
6410                    },
6411                ]
6412            }),
6413            Ok(())
6414        ),
6415        test_cml_child_name_invalid(
6416            json!({
6417                "children": [
6418                    {
6419                        "name": "/bad",
6420                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
6421                    },
6422                ]
6423            }),
6424            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"/bad\", expected a \
6425            name that consists of [A-Za-z0-9_.-] and starts with [A-Za-z0-9_]"
6426        ),
6427        test_cml_child_name_too_long(
6428            json!({
6429                "children": [
6430                    {
6431                        "name": "a".repeat(256),
6432                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
6433                    }
6434                ]
6435            }),
6436            Err(Error::Parse { err, .. }) if &err == "invalid length 256, expected a non-empty name no more than 255 characters in length"
6437        ),
6438        test_cml_url(
6439            json!({
6440                "children": [
6441                    {
6442                        "name": "logger",
6443                        "url": "my+awesome-scheme.2://abc123!@$%.com",
6444                    },
6445                ]
6446            }),
6447            Ok(())
6448        ),
6449        test_cml_url_host_pound_invalid(
6450            json!({
6451                "children": [
6452                    {
6453                        "name": "logger",
6454                        "url": "my+awesome-scheme.2://abc123!@#$%.com",
6455                    },
6456                ]
6457            }),
6458            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"my+awesome-scheme.2://abc123!@#$%.com\", expected a valid URL"
6459        ),
6460        test_cml_url_invalid(
6461            json!({
6462                "children": [
6463                    {
6464                        "name": "logger",
6465                        "url": "fuchsia-pkg",
6466                    },
6467                ]
6468            }),
6469            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"fuchsia-pkg\", expected a valid URL"
6470        ),
6471        test_cml_url_too_long(
6472            json!({
6473                "children": [
6474                    {
6475                        "name": "logger",
6476                        "url": &format!("fuchsia-pkg://{}", "a".repeat(4083)),
6477                    },
6478                ]
6479            }),
6480            Err(Error::Parse { err, .. }) if &err == "invalid length 4097, expected a non-empty URL no more than 4096 characters in length"
6481        ),
6482        test_cml_duplicate_identifiers_children_collection(
6483           json!({
6484               "children": [
6485                    {
6486                        "name": "logger",
6487                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
6488                    }
6489               ],
6490               "collections": [
6491                   {
6492                       "name": "logger",
6493                       "durability": "transient"
6494                   }
6495               ]
6496           }),
6497           Err(Error::Validate { err, .. }) if &err == "identifier \"logger\" is defined twice, once in \"collections\" and once in \"children\""
6498        ),
6499        test_cml_duplicate_identifiers_children_storage(
6500           json!({
6501               "children": [
6502                    {
6503                        "name": "logger",
6504                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
6505                    }
6506               ],
6507               "capabilities": [
6508                    {
6509                        "storage": "logger",
6510                        "path": "/logs",
6511                        "from": "parent"
6512                    }
6513                ]
6514           }),
6515           Err(Error::Validate { err, .. }) if &err == "identifier \"logger\" is defined twice, once in \"storage\" and once in \"children\""
6516        ),
6517        test_cml_duplicate_identifiers_collection_storage(
6518           json!({
6519               "collections": [
6520                    {
6521                        "name": "logger",
6522                        "durability": "transient"
6523                    }
6524                ],
6525                "capabilities": [
6526                    {
6527                        "storage": "logger",
6528                        "path": "/logs",
6529                        "from": "parent"
6530                    }
6531                ]
6532           }),
6533           Err(Error::Validate { err, .. }) if &err == "identifier \"logger\" is defined twice, once in \"storage\" and once in \"collections\""
6534        ),
6535        test_cml_duplicate_identifiers_children_runners(
6536           json!({
6537               "children": [
6538                    {
6539                        "name": "logger",
6540                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
6541                    }
6542               ],
6543               "capabilities": [
6544                    {
6545                        "runner": "logger",
6546                        "from": "parent"
6547                    }
6548                ]
6549           }),
6550           Err(Error::Validate { err, .. }) if &err == "identifier \"logger\" is defined twice, once in \"runners\" and once in \"children\""
6551        ),
6552        test_cml_duplicate_identifiers_environments(
6553            json!({
6554                "children": [
6555                     {
6556                         "name": "logger",
6557                         "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
6558                     }
6559                ],
6560                "environments": [
6561                     {
6562                         "name": "logger",
6563                     }
6564                 ]
6565            }),
6566            Err(Error::Validate { err, .. }) if &err == "identifier \"logger\" is defined twice, once in \"environments\" and once in \"children\""
6567        ),
6568
6569        // deny unknown fields
6570        test_deny_unknown_fields(
6571            json!(
6572                {
6573                    "program": {
6574                        "runner": "elf",
6575                        "binary": "bin/app",
6576                    },
6577                    "unknown_field": {},
6578                }
6579            ),
6580            Err(Error::Parse { err, .. }) if err.starts_with("unknown field `unknown_field`, expected one of ")
6581        ),
6582        test_offer_source_availability_unknown(
6583            json!({
6584                "children": [
6585                    {
6586                        "name": "foo",
6587                        "url": "fuchsia-pkg://foo.com/foo#meta/foo.cm"
6588                    },
6589                ],
6590                "offer": [
6591                    {
6592                        "protocol": "fuchsia.examples.Echo",
6593                        "from": "#bar",
6594                        "to": "#foo",
6595                        "availability": "optional",
6596                        "source_availability": "unknown",
6597                    },
6598                ],
6599            }),
6600            Ok(())
6601        ),
6602        test_offer_source_availability_required(
6603            json!({
6604                "children": [
6605                    {
6606                        "name": "foo",
6607                        "url": "fuchsia-pkg://foo.com/foo#meta/foo.cm"
6608                    },
6609                ],
6610                "offer": [
6611                    {
6612                        "protocol": "fuchsia.examples.Echo",
6613                        "from": "#bar",
6614                        "to": "#foo",
6615                        "source_availability": "required",
6616                    },
6617                ],
6618            }),
6619            Err(Error::Validate { err, .. }) if &err == "\"offer\" source \"#bar\" does not appear in \"children\" or \"capabilities\""
6620        ),
6621        test_offer_source_availability_omitted(
6622            json!({
6623                "children": [
6624                    {
6625                        "name": "foo",
6626                        "url": "fuchsia-pkg://foo.com/foo#meta/foo.cm"
6627                    },
6628                ],
6629                "offer": [
6630                    {
6631                        "protocol": "fuchsia.examples.Echo",
6632                        "from": "#bar",
6633                        "to": "#foo",
6634                    },
6635                ],
6636            }),
6637            Err(Error::Validate { err, .. }) if &err == "\"offer\" source \"#bar\" does not appear in \"children\" or \"capabilities\""
6638        ),
6639        test_cml_use_invalid_availability(
6640            json!({
6641                "use": [
6642                    {
6643                        "protocol": "fuchsia.examples.Echo",
6644                        "availability": "same_as_target",
6645                    },
6646                ],
6647            }),
6648            Err(Error::Validate { err, .. }) if &err == "\"availability: same_as_target\" cannot be used with use declarations"
6649        ),
6650        test_offer_source_void_availability_required(
6651            json!({
6652                "children": [
6653                    {
6654                        "name": "foo",
6655                        "url": "fuchsia-pkg://foo.com/foo#meta/foo.cm"
6656                    },
6657                ],
6658                "offer": [
6659                    {
6660                        "protocol": "fuchsia.examples.Echo",
6661                        "from": "void",
6662                        "to": "#foo",
6663                        "availability": "required",
6664                    },
6665                ],
6666            }),
6667            Err(Error::Validate { err, .. }) if &err == "capabilities with a source of \"void\" must have an availability of \"optional\", capabilities: \"fuchsia.examples.Echo\", from: \"void\""
6668        ),
6669        test_offer_source_void_availability_same_as_target(
6670            json!({
6671                "children": [
6672                    {
6673                        "name": "foo",
6674                        "url": "fuchsia-pkg://foo.com/foo#meta/foo.cm"
6675                    },
6676                ],
6677                "offer": [
6678                    {
6679                        "protocol": "fuchsia.examples.Echo",
6680                        "from": "void",
6681                        "to": "#foo",
6682                        "availability": "same_as_target",
6683                    },
6684                ],
6685            }),
6686            Err(Error::Validate { err, .. }) if &err == "capabilities with a source of \"void\" must have an availability of \"optional\", capabilities: \"fuchsia.examples.Echo\", from: \"void\""
6687        ),
6688        test_offer_source_missing_availability_required(
6689            json!({
6690                "children": [
6691                    {
6692                        "name": "foo",
6693                        "url": "fuchsia-pkg://foo.com/foo#meta/foo.cm"
6694                    },
6695                ],
6696                "offer": [
6697                    {
6698                        "protocol": "fuchsia.examples.Echo",
6699                        "from": "#bar",
6700                        "to": "#foo",
6701                        "availability": "required",
6702                        "source_availability": "unknown",
6703                    },
6704                ],
6705            }),
6706            Err(Error::Validate { err, .. }) if &err == "capabilities with an intentionally missing source must have an availability that is either unset or \"optional\", capabilities: \"fuchsia.examples.Echo\", from: \"#bar\""
6707        ),
6708        test_offer_source_missing_availability_same_as_target(
6709            json!({
6710                "children": [
6711                    {
6712                        "name": "foo",
6713                        "url": "fuchsia-pkg://foo.com/foo#meta/foo.cm"
6714                    },
6715                ],
6716                "offer": [
6717                    {
6718                        "protocol": "fuchsia.examples.Echo",
6719                        "from": "#bar",
6720                        "to": "#foo",
6721                        "availability": "same_as_target",
6722                        "source_availability": "unknown",
6723                    },
6724                ],
6725            }),
6726            Err(Error::Validate { err, .. }) if &err == "capabilities with an intentionally missing source must have an availability that is either unset or \"optional\", capabilities: \"fuchsia.examples.Echo\", from: \"#bar\""
6727        ),
6728        test_expose_source_availability_unknown(
6729            json!({
6730                "expose": [
6731                    {
6732                        "protocol": "fuchsia.examples.Echo",
6733                        "from": "#bar",
6734                        "availability": "optional",
6735                        "source_availability": "unknown",
6736                    },
6737                ],
6738            }),
6739            Ok(())
6740        ),
6741        test_expose_source_availability_required(
6742            json!({
6743                "expose": [
6744                    {
6745                        "protocol": "fuchsia.examples.Echo",
6746                        "from": "#bar",
6747                        "source_availability": "required",
6748                    },
6749                ],
6750            }),
6751            Err(Error::Validate { err, .. }) if &err == "\"expose\" source \"#bar\" does not appear in \"children\" or \"capabilities\""
6752        ),
6753        test_expose_source_availability_omitted(
6754            json!({
6755                "expose": [
6756                    {
6757                        "protocol": "fuchsia.examples.Echo",
6758                        "from": "#bar",
6759                    },
6760                ],
6761            }),
6762            Err(Error::Validate { err, .. }) if &err == "\"expose\" source \"#bar\" does not appear in \"children\" or \"capabilities\""
6763        ),
6764        test_expose_source_void_availability_required(
6765            json!({
6766                "expose": [
6767                    {
6768                        "protocol": "fuchsia.examples.Echo",
6769                        "from": "void",
6770                        "availability": "required",
6771                    },
6772                ],
6773            }),
6774            Err(Error::Validate { err, .. }) if &err == "capabilities with a source of \"void\" must have an availability of \"optional\", capabilities: \"fuchsia.examples.Echo\", from: \"void\""
6775        ),
6776        test_expose_source_void_availability_same_as_target(
6777            json!({
6778                "expose": [
6779                    {
6780                        "protocol": "fuchsia.examples.Echo",
6781                        "from": "void",
6782                        "availability": "same_as_target",
6783                    },
6784                ],
6785            }),
6786            Err(Error::Validate { err, .. }) if &err == "capabilities with a source of \"void\" must have an availability of \"optional\", capabilities: \"fuchsia.examples.Echo\", from: \"void\""
6787        ),
6788        test_expose_source_missing_availability_required(
6789            json!({
6790                "expose": [
6791                    {
6792                        "protocol": "fuchsia.examples.Echo",
6793                        "from": "#bar",
6794                        "availability": "required",
6795                        "source_availability": "unknown",
6796                    },
6797                ],
6798            }),
6799            Err(Error::Validate { err, .. }) if &err == "capabilities with an intentionally missing source must have an availability that is either unset or \"optional\", capabilities: \"fuchsia.examples.Echo\", from: \"#bar\""
6800        ),
6801        test_expose_source_missing_availability_same_as_target(
6802            json!({
6803                "expose": [
6804                    {
6805                        "protocol": "fuchsia.examples.Echo",
6806                        "from": "#bar",
6807                        "availability": "same_as_target",
6808                        "source_availability": "unknown",
6809                    },
6810                ],
6811            }),
6812            Err(Error::Validate { err, .. }) if &err == "capabilities with an intentionally missing source must have an availability that is either unset or \"optional\", capabilities: \"fuchsia.examples.Echo\", from: \"#bar\""
6813        ),
6814    }
6815
6816    // Tests for services.
6817    test_validate_cml! {
6818        test_cml_validate_use_service(
6819            json!({
6820                "use": [
6821                    { "service": "CoolFonts", "path": "/svc/fuchsia.fonts.Provider" },
6822                    { "service": "fuchsia.component.Realm", "from": "framework" },
6823                ],
6824            }),
6825            Ok(())
6826        ),
6827        test_cml_use_invalid_from_with_service(
6828            json!({
6829                "use": [ { "service": "foo", "from": "debug" } ]
6830            }),
6831            Err(Error::Validate { err, .. }) if &err == "only \"protocol\" supports source from \"debug\""
6832        ),
6833        test_cml_validate_offer_service(
6834            json!({
6835                "offer": [
6836                    {
6837                        "service": "fuchsia.logger.Log",
6838                        "from": "#logger",
6839                        "to": [ "#echo_server", "#modular" ],
6840                        "as": "fuchsia.logger.SysLog"
6841                    },
6842                    {
6843                        "service": "fuchsia.fonts.Provider",
6844                        "from": "parent",
6845                        "to": [ "#echo_server" ]
6846                    },
6847                    {
6848                        "service": "fuchsia.net.Netstack",
6849                        "from": "self",
6850                        "to": [ "#echo_server" ]
6851                    },
6852                ],
6853                "children": [
6854                    {
6855                        "name": "logger",
6856                        "url": "fuchsia-pkg://logger",
6857                    },
6858                    {
6859                        "name": "echo_server",
6860                        "url": "fuchsia-pkg://echo_server",
6861                    }
6862                ],
6863                "collections": [
6864                    {
6865                        "name": "modular",
6866                        "durability": "transient",
6867                    },
6868                ],
6869                "capabilities": [
6870                    { "service": "fuchsia.net.Netstack" },
6871                ],
6872            }),
6873            Ok(())
6874        ),
6875        test_cml_validate_expose_service(
6876            json!(
6877                {
6878                    "expose": [
6879                        {
6880                            "service": "fuchsia.fonts.Provider",
6881                            "from": "self",
6882                        },
6883                        {
6884                            "service": "fuchsia.logger.Log",
6885                            "from": "#logger",
6886                            "as": "logger"
6887                        },
6888                    ],
6889                    "capabilities": [
6890                        { "service": "fuchsia.fonts.Provider" },
6891                    ],
6892                    "children": [
6893                        {
6894                            "name": "logger",
6895                            "url": "fuchsia-pkg://logger",
6896                        },
6897                    ]
6898                }
6899            ),
6900            Ok(())
6901        ),
6902        test_cml_validate_expose_service_multi_source(
6903            json!(
6904                {
6905                    "expose": [
6906                        {
6907                            "service": "fuchsia.my.Service",
6908                            "from": [ "self", "#a" ],
6909                        },
6910                        {
6911                            "service": "fuchsia.my.Service",
6912                            "from": "#coll",
6913                        },
6914                    ],
6915                    "capabilities": [
6916                        { "service": "fuchsia.my.Service" },
6917                    ],
6918                    "children": [
6919                        {
6920                            "name": "a",
6921                            "url": "fuchsia-pkg://a",
6922                        },
6923                    ],
6924                    "collections": [
6925                        {
6926                            "name": "coll",
6927                            "durability": "transient",
6928                        },
6929                    ],
6930                }
6931            ),
6932            Ok(())
6933        ),
6934        test_cml_validate_offer_service_multi_source(
6935            json!(
6936                {
6937                    "offer": [
6938                        {
6939                            "service": "fuchsia.my.Service",
6940                            "from": [ "self", "parent" ],
6941                            "to": "#b",
6942                        },
6943                        {
6944                            "service": "fuchsia.my.Service",
6945                            "from": [ "#a", "#coll" ],
6946                            "to": "#b",
6947                        },
6948                    ],
6949                    "capabilities": [
6950                        { "service": "fuchsia.my.Service" },
6951                    ],
6952                    "children": [
6953                        {
6954                            "name": "a",
6955                            "url": "fuchsia-pkg://a",
6956                        },
6957                        {
6958                            "name": "b",
6959                            "url": "fuchsia-pkg://b",
6960                        },
6961                    ],
6962                    "collections": [
6963                        {
6964                            "name": "coll",
6965                            "durability": "transient",
6966                        },
6967                    ],
6968                }
6969            ),
6970            Ok(())
6971        ),
6972        test_cml_service(
6973            json!({
6974                "capabilities": [
6975                    {
6976                        "protocol": "a",
6977                        "path": "/minfs",
6978                    },
6979                    {
6980                        "protocol": "b",
6981                        "path": "/data",
6982                    },
6983                    {
6984                        "protocol": "c",
6985                    },
6986                ],
6987            }),
6988            Ok(())
6989        ),
6990        test_cml_service_multi(
6991            json!({
6992                "capabilities": [
6993                    {
6994                        "service": ["a", "b", "c"],
6995                    },
6996                ],
6997            }),
6998            Ok(())
6999        ),
7000        test_cml_service_multi_invalid_path(
7001            json!({
7002                "capabilities": [
7003                    {
7004                        "service": ["a", "b", "c"],
7005                        "path": "/minfs",
7006                    },
7007                ],
7008            }),
7009            Err(Error::Validate { err, .. }) if &err == "\"path\" can only be specified when one `service` is supplied."
7010        ),
7011        test_cml_service_all_valid_chars(
7012            json!({
7013                "capabilities": [
7014                    {
7015                        "service": "abcdefghijklmnopqrstuvwxyz0123456789_-service",
7016                    },
7017                ],
7018            }),
7019            Ok(())
7020        ),
7021    }
7022
7023    // Tests structured config
7024    test_validate_cml_with_feature! { FeatureSet::from(vec![]), {
7025        test_cml_configs(
7026            json!({
7027                "config": {
7028                    "verbosity": {
7029                        "type": "string",
7030                        "max_size": 20,
7031                    },
7032                    "timeout": { "type": "uint64" },
7033                    "tags": {
7034                        "type": "vector",
7035                        "max_count": 10,
7036                        "element": {
7037                            "type": "string",
7038                            "max_size": 50
7039                        }
7040                    }
7041                }
7042            }),
7043            Ok(())
7044        ),
7045
7046        test_cml_configs_not_object(
7047            json!({
7048                "config": "abcd"
7049            }),
7050            Err(Error::Parse { err, .. }) if &err == "invalid type: string \"abcd\", expected a map"
7051        ),
7052
7053        test_cml_configs_empty(
7054            json!({
7055                "config": {
7056                }
7057            }),
7058            Err(Error::Validate { err, .. }) if &err == "'config' section is empty"
7059        ),
7060
7061        test_cml_configs_bad_type(
7062            json!({
7063                "config": {
7064                    "verbosity": 123456
7065                }
7066            }),
7067            Err(Error::Parse { err, .. }) if &err == "invalid type: integer `123456`, expected internally tagged enum ConfigValueType"
7068        ),
7069
7070        test_cml_configs_unknown_type(
7071            json!({
7072                "config": {
7073                    "verbosity": {
7074                        "type": "foo"
7075                    }
7076                }
7077            }),
7078            Err(Error::Parse { err, .. }) if &err == "unknown variant `foo`, expected one of `bool`, `uint8`, `uint16`, `uint32`, `uint64`, `int8`, `int16`, `int32`, `int64`, `string`, `vector`"
7079        ),
7080
7081        test_cml_configs_no_max_count_vector(
7082            json!({
7083                "config": {
7084                    "tags": {
7085                        "type": "vector",
7086                        "element": {
7087                            "type": "string",
7088                            "max_size": 50,
7089                        }
7090                    }
7091                }
7092            }),
7093            Err(Error::Parse { err, .. }) if &err == "missing field `max_count`"
7094        ),
7095
7096        test_cml_configs_no_max_size_string(
7097            json!({
7098                "config": {
7099                    "verbosity": {
7100                        "type": "string",
7101                    }
7102                }
7103            }),
7104            Err(Error::Parse { err, .. }) if &err == "missing field `max_size`"
7105        ),
7106
7107        test_cml_configs_no_max_size_string_vector(
7108            json!({
7109                "config": {
7110                    "tags": {
7111                        "type": "vector",
7112                        "max_count": 10,
7113                        "element": {
7114                            "type": "string",
7115                        }
7116                    }
7117                }
7118            }),
7119            Err(Error::Parse { err, .. }) if &err == "missing field `max_size`"
7120        ),
7121
7122        test_cml_configs_empty_key(
7123            json!({
7124                "config": {
7125                    "": {
7126                        "type": "bool"
7127                    }
7128                }
7129            }),
7130            Err(Error::Parse { err, .. }) if &err == "invalid length 0, expected a non-empty name no more than 64 characters in length"
7131        ),
7132
7133        test_cml_configs_too_long_key(
7134            json!({
7135                "config": {
7136                    "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": {
7137                        "type": "bool"
7138                    }
7139                }
7140            }),
7141            Err(Error::Parse { err, .. }) if &err == "invalid length 74, expected a non-empty name no more than 64 characters in length"
7142        ),
7143        test_cml_configs_key_starts_with_number(
7144            json!({
7145                "config": {
7146                    "8abcd": { "type": "uint8" }
7147                }
7148            }),
7149            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"8abcd\", expected a name which must start with a letter, can contain letters, numbers, and underscores, but cannot end with an underscore"
7150        ),
7151
7152        test_cml_configs_key_ends_with_underscore(
7153            json!({
7154                "config": {
7155                    "abcd_": { "type": "uint8" }
7156                }
7157            }),
7158            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"abcd_\", expected a name which must start with a letter, can contain letters, numbers, and underscores, but cannot end with an underscore"
7159        ),
7160
7161        test_cml_configs_capitals_in_key(
7162            json!({
7163                "config": {
7164                    "ABCD": { "type": "uint8" }
7165                }
7166            }),
7167            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"ABCD\", expected a name which must start with a letter, can contain letters, numbers, and underscores, but cannot end with an underscore"
7168        ),
7169
7170        test_cml_configs_special_chars_in_key(
7171            json!({
7172                "config": {
7173                    "!@#$": { "type": "uint8" }
7174                }
7175            }),
7176            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"!@#$\", expected a name which must start with a letter, can contain letters, numbers, and underscores, but cannot end with an underscore"
7177        ),
7178
7179        test_cml_configs_dashes_in_key(
7180            json!({
7181                "config": {
7182                    "abcd-efgh": { "type": "uint8" }
7183                }
7184            }),
7185            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"abcd-efgh\", expected a name which must start with a letter, can contain letters, numbers, and underscores, but cannot end with an underscore"
7186        ),
7187
7188        test_cml_configs_bad_max_size_string(
7189            json!({
7190                "config": {
7191                    "verbosity": {
7192                        "type": "string",
7193                        "max_size": "abcd"
7194                    }
7195                }
7196            }),
7197            Err(Error::Parse { err, .. }) if &err == "invalid type: string \"abcd\", expected a nonzero u32"
7198        ),
7199
7200        test_cml_configs_zero_max_size_string(
7201            json!({
7202                "config": {
7203                    "verbosity": {
7204                        "type": "string",
7205                        "max_size": 0
7206                    }
7207                }
7208            }),
7209            Err(Error::Parse { err, .. }) if &err == "invalid value: integer `0`, expected a nonzero u32"
7210        ),
7211
7212        test_cml_configs_bad_max_count_on_vector(
7213            json!({
7214                "config": {
7215                    "toggles": {
7216                        "type": "vector",
7217                        "max_count": "abcd",
7218                        "element": {
7219                            "type": "bool"
7220                        }
7221                    }
7222                }
7223            }),
7224            Err(Error::Parse { err, .. }) if &err == "invalid type: string \"abcd\", expected a nonzero u32"
7225        ),
7226
7227        test_cml_configs_zero_max_count_on_vector(
7228            json!({
7229                "config": {
7230                    "toggles": {
7231                        "type": "vector",
7232                        "max_count": 0,
7233                        "element": {
7234                            "type": "bool"
7235                        }
7236                    }
7237                }
7238            }),
7239            Err(Error::Parse { err, .. }) if &err == "invalid value: integer `0`, expected a nonzero u32"
7240        ),
7241
7242        test_cml_configs_bad_max_size_string_vector(
7243            json!({
7244                "config": {
7245                    "toggles": {
7246                        "type": "vector",
7247                        "max_count": 100,
7248                        "element": {
7249                            "type": "string",
7250                            "max_size": "abcd"
7251                        }
7252                    }
7253                }
7254            }),
7255            Err(Error::Parse { err, .. }) if &err == "invalid type: string \"abcd\", expected a nonzero u32"
7256        ),
7257
7258        test_cml_configs_zero_max_size_string_vector(
7259            json!({
7260                "config": {
7261                    "toggles": {
7262                        "type": "vector",
7263                        "max_count": 100,
7264                        "element": {
7265                            "type": "string",
7266                            "max_size": 0
7267                        }
7268                    }
7269                }
7270            }),
7271            Err(Error::Parse { err, .. }) if &err == "invalid value: integer `0`, expected a nonzero u32"
7272        ),
7273    }}
7274
7275    // Tests the use of `allow_long_names` when the "AllowLongNames" feature is set.
7276    test_validate_cml_with_feature! { FeatureSet::from(vec![Feature::AllowLongNames]), {
7277        test_cml_validate_set_allow_long_names_true(
7278            json!({
7279                "collections": [
7280                    {
7281                        "name": "foo",
7282                        "durability": "transient",
7283                        "allow_long_names": true
7284                    },
7285                ],
7286            }),
7287            Ok(())
7288        ),
7289        test_cml_validate_set_allow_long_names_false(
7290            json!({
7291                "collections": [
7292                    {
7293                        "name": "foo",
7294                        "durability": "transient",
7295                        "allow_long_names": false
7296                    },
7297                ],
7298            }),
7299            Ok(())
7300        ),
7301    }}
7302
7303    // Tests that the use of `allow_long_names` fails when the "AllowLongNames"
7304    // feature is not set.
7305    test_validate_cml! {
7306        test_cml_allow_long_names_without_feature(
7307            json!({
7308                "collections": [
7309                    {
7310                        "name": "foo",
7311                        "durability": "transient",
7312                        "allow_long_names": true
7313                    },
7314                ],
7315            }),
7316            Err(Error::RestrictedFeature(s)) if s == "allow_long_names"
7317        ),
7318    }
7319
7320    // Tests validate_facets function without the feature set
7321    test_validate_cml! {
7322        test_valid_empty_facets(
7323            json!({
7324                "facets": {}
7325            }),
7326            Ok(())
7327        ),
7328
7329        test_invalid_empty_facets(
7330            json!({
7331                "facets": ""
7332            }),
7333            Err(err) if err.to_string().contains("invalid type: string")
7334        ),
7335        test_valid_empty_fuchsia_test_facet(
7336            json!({
7337                "facets": {TEST_FACET_KEY: {}}
7338            }),
7339            Ok(())
7340        ),
7341
7342        test_valid_allowed_pkg_without_feature(
7343            json!({
7344                "facets": {
7345                    TEST_TYPE_FACET_KEY: "some_realm",
7346                    TEST_FACET_KEY: {
7347                        TEST_DEPRECATED_ALLOWED_PACKAGES_FACET_KEY: [ "some_pkg" ]
7348                    }
7349                }
7350            }),
7351            Ok(())
7352        ),
7353    }
7354
7355    // Tests validate_facets function with the RestrictTestTypeInFacet enabled.
7356    test_validate_cml_with_feature! { FeatureSet::from(vec![Feature::RestrictTestTypeInFacet]), {
7357        test_valid_empty_facets_with_test_type_feature_enabled(
7358            json!({
7359                "facets": {}
7360            }),
7361            Ok(())
7362        ),
7363        test_valid_empty_fuchsia_test_facet_with_test_type_feature_enabled(
7364            json!({
7365                "facets": {TEST_FACET_KEY: {}}
7366            }),
7367            Ok(())
7368        ),
7369
7370        test_invalid_test_type_with_feature_enabled(
7371            json!({
7372                "facets": {
7373                    TEST_FACET_KEY: {
7374                        TEST_TYPE_FACET_KEY: "some_realm",
7375                    }
7376                }
7377            }),
7378            Err(err) if err.to_string().contains(TEST_TYPE_FACET_KEY)
7379        ),
7380    }}
7381
7382    // Tests validate_facets function with the EnableAllowNonHermeticPackagesFeature disabled.
7383    test_validate_cml_with_feature! { FeatureSet::from(vec![Feature::AllowNonHermeticPackages]), {
7384        test_valid_empty_facets_with_feature_disabled(
7385            json!({
7386                "facets": {}
7387            }),
7388            Ok(())
7389        ),
7390        test_valid_empty_fuchsia_test_facet_with_feature_disabled(
7391            json!({
7392                "facets": {TEST_FACET_KEY: {}}
7393            }),
7394            Ok(())
7395        ),
7396
7397        test_valid_allowed_pkg_with_feature_disabled(
7398            json!({
7399                "facets": {
7400                    TEST_FACET_KEY: {
7401                        TEST_DEPRECATED_ALLOWED_PACKAGES_FACET_KEY: [ "some_pkg" ]
7402                    }
7403                }
7404            }),
7405            Ok(())
7406        ),
7407    }}
7408
7409    // Tests validate_facets function with the EnableAllowNonHermeticPackagesFeature enabled.
7410    test_validate_cml_with_feature! { FeatureSet::from(vec![Feature::EnableAllowNonHermeticPackagesFeature]), {
7411        test_valid_empty_facets_with_feature_enabled(
7412            json!({
7413                "facets": {}
7414            }),
7415            Ok(())
7416        ),
7417        test_valid_empty_fuchsia_test_facet_with_feature_enabled(
7418            json!({
7419                "facets": {TEST_FACET_KEY: {}}
7420            }),
7421            Ok(())
7422        ),
7423
7424        test_invalid_allowed_pkg_with_feature_enabled(
7425            json!({
7426                "facets": {
7427                    TEST_FACET_KEY: {
7428                        TEST_DEPRECATED_ALLOWED_PACKAGES_FACET_KEY: [ "some_pkg" ]
7429                    }
7430                }
7431            }),
7432            Err(err) if err.to_string().contains(&Feature::AllowNonHermeticPackages.to_string())
7433        ),
7434    }}
7435
7436    // Tests validate_facets function with the feature enabled and allowed pkg feature set.
7437    test_validate_cml_with_feature! { FeatureSet::from(vec![Feature::EnableAllowNonHermeticPackagesFeature, Feature::AllowNonHermeticPackages]), {
7438        test_invalid_empty_facets_with_feature_enabled(
7439            json!({
7440                "facets": {}
7441            }),
7442            Err(err) if err.to_string().contains(&Feature::AllowNonHermeticPackages.to_string())
7443        ),
7444        test_invalid_empty_fuchsia_test_facet_with_feature_enabled(
7445            json!({
7446                "facets": {TEST_FACET_KEY: {}}
7447            }),
7448            Err(err) if err.to_string().contains(&Feature::AllowNonHermeticPackages.to_string())
7449        ),
7450
7451        test_valid_allowed_pkg_with_feature_enabled(
7452            json!({
7453                "facets": {
7454                    TEST_FACET_KEY: {
7455                        TEST_DEPRECATED_ALLOWED_PACKAGES_FACET_KEY: [ "some_pkg" ]
7456                    }
7457                }
7458            }),
7459            Ok(())
7460        ),
7461    }}
7462
7463    test_validate_cml_with_feature! { FeatureSet::from(vec![Feature::DynamicDictionaries]), {
7464        test_cml_offer_to_dictionary_unsupported(
7465            json!({
7466                "offer": [
7467                    {
7468                        "event_stream": "p",
7469                        "from": "parent",
7470                        "to": "self/dict",
7471                    },
7472                ],
7473                "capabilities": [
7474                    {
7475                        "dictionary": "dict",
7476                    },
7477                ],
7478            }),
7479            Err(Error::Validate { err, .. }) if &err == "\"offer\" to dictionary \
7480            \"self/dict\" for \"event_stream\" but dictionaries do not support this type yet."
7481        ),
7482        test_cml_dictionary_ref(
7483            json!({
7484                "use": [
7485                    {
7486                        "protocol": "a",
7487                        "from": "parent/a",
7488                    },
7489                    {
7490                        "protocol": "b",
7491                        "from": "#child/a/b",
7492                    },
7493                    {
7494                        "protocol": "c",
7495                        "from": "self/a/b/c",
7496                    },
7497                ],
7498                "capabilities": [
7499                    {
7500                        "dictionary": "a",
7501                    },
7502                ],
7503                "children": [
7504                    {
7505                        "name": "child",
7506                        "url": "fuchsia-pkg://child",
7507                    },
7508                ],
7509            }),
7510            Ok(())
7511        ),
7512        test_cml_use_dictionary_disallowed(
7513            json!({
7514                "use": [
7515                    {
7516                        "dictionary": "dict",
7517                    },
7518                ],
7519            }),
7520            Err(Error::Parse { err, .. }) if err.starts_with("unknown field `dictionary`")
7521        ),
7522        test_cml_expose_dictionary_from_self(
7523            json!({
7524                "expose": [
7525                    {
7526                        "dictionary": "foo_dictionary",
7527                        "from": "self",
7528                    },
7529                ],
7530                "capabilities": [
7531                    {
7532                        "dictionary": "foo_dictionary",
7533                    },
7534                ]
7535            }),
7536            Ok(())
7537        ),
7538        test_cml_offer_to_dictionary_duplicate(
7539            json!({
7540                "offer": [
7541                    {
7542                        "protocol": "p",
7543                        "from": "parent",
7544                        "to": "self/dict",
7545                    },
7546                    {
7547                        "protocol": "p",
7548                        "from": "#child",
7549                        "to": "self/dict",
7550                    },
7551                ],
7552                "capabilities": [
7553                    {
7554                        "dictionary": "dict",
7555                    },
7556                ],
7557                "children": [
7558                    {
7559                        "name": "child",
7560                        "url": "fuchsia-pkg://child",
7561                    },
7562                ],
7563            }),
7564            Err(Error::Validate { err, .. }) if &err == "\"p\" is a duplicate \"offer\" target capability for \"self/dict\""
7565        ),
7566        test_cml_offer_to_dictionary_dynamic(
7567            json!({
7568                "offer": [
7569                    {
7570                        "protocol": "p",
7571                        "from": "parent",
7572                        "to": "self/dict",
7573                    },
7574                ],
7575                "capabilities": [
7576                    {
7577                        "dictionary": "dict",
7578                        "path": "/out/dir",
7579                    },
7580                ],
7581            }),
7582            Err(Error::Validate { err, .. }) if &err == "\"offer\" has dictionary target \"self/dict\" but \"dict\" sets \"path\". Therefore, it is a dynamic dictionary that does not allow offers into it."
7583        ),
7584        test_cml_offer_dependency_cycle_from_dictionary(
7585            json!({
7586                    "offer": [
7587                        {
7588                            "protocol": "1",
7589                            "from": "#a/in/dict",
7590                            "to": [ "#b" ],
7591                            "dependency": "strong"
7592                        },
7593                        {
7594                            "directory": "2",
7595                            "from": "#b/in/dict",
7596                            "to": [ "#a" ],
7597                        },
7598                    ],
7599                    "children": [
7600                        {
7601                            "name": "a",
7602                            "url": "fuchsia-pkg://fuchsia.com/a#meta/a.cm"
7603                        },
7604                        {
7605                            "name": "b",
7606                            "url": "fuchsia-pkg://fuchsia.com/b#meta/b.cm"
7607                        },
7608                    ]
7609                }),
7610            Err(Error::Validate {
7611                err,
7612                ..
7613            }) if &err ==
7614                "Strong dependency cycles were found. Break the cycle by removing a \
7615                dependency or marking an offer as weak. Cycles: \
7616                {{#a -> #b -> #a}}"
7617        ),
7618        test_cml_offer_dependency_cycle_with_dictionary(
7619            json!({
7620                "capabilities": [
7621                    {
7622                        "dictionary": "dict",
7623                    },
7624                ],
7625                "children": [
7626                    {
7627                        "name": "a",
7628                        "url": "#meta/a.cm",
7629                    },
7630                    {
7631                        "name": "b",
7632                        "url": "#meta/b.cm",
7633                    },
7634                ],
7635                "offer": [
7636                    {
7637                        "dictionary": "dict",
7638                        "from": "self",
7639                        "to": "#a",
7640                    },
7641                    {
7642                        "protocol": "1",
7643                        "from": "#b",
7644                        "to": "self/dict",
7645                    },
7646                    {
7647                        "protocol": "2",
7648                        "from": "#a",
7649                        "to": "#b",
7650                    },
7651                ],
7652            }),
7653            Err(Error::Validate {
7654                err,
7655                ..
7656            }) if &err ==
7657                "Strong dependency cycles were found. Break the cycle by removing a \
7658                dependency or marking an offer as weak. Cycles: {{#a -> #b -> #dict -> #a}}"
7659        ),
7660        test_cml_offer_dependency_cycle_with_dictionary_indirect(
7661            json!({
7662                "capabilities": [
7663                    {
7664                        "dictionary": "dict",
7665                    },
7666                ],
7667                "children": [
7668                    {
7669                        "name": "a",
7670                        "url": "#meta/a.cm",
7671                    },
7672                    {
7673                        "name": "b",
7674                        "url": "#meta/b.cm",
7675                    },
7676                ],
7677                "offer": [
7678                    {
7679                        "protocol": "3",
7680                        "from": "self/dict",
7681                        "to": "#a",
7682                    },
7683                    {
7684                        "protocol": "1",
7685                        "from": "#b",
7686                        "to": "self/dict",
7687                    },
7688                    {
7689                        "protocol": "2",
7690                        "from": "#a",
7691                        "to": "#b",
7692                    },
7693                ],
7694            }),
7695            Err(Error::Validate {
7696                err,
7697                ..
7698            }) if &err ==
7699                "Strong dependency cycles were found. Break the cycle by removing a \
7700                dependency or marking an offer as weak. Cycles: {{#a -> #b -> #dict -> #a}}"
7701        ),
7702        test_cml_use_dependency_cycle_with_dictionary(
7703            json!({
7704                "capabilities": [
7705                    {
7706                        "protocol": "1",
7707                    },
7708                    {
7709                        "dictionary": "dict",
7710                    },
7711                ],
7712                "children": [
7713                    {
7714                        "name": "a",
7715                        "url": "#meta/a.cm",
7716                    },
7717                ],
7718                "use": [
7719                    {
7720                        "protocol": "2",
7721                        "from": "#a",
7722                    },
7723                ],
7724                "offer": [
7725                    {
7726                        "dictionary": "dict",
7727                        "from": "self",
7728                        "to": "#a",
7729                    },
7730                    {
7731                        "protocol": "1",
7732                        "from": "self",
7733                        "to": "self/dict",
7734                    },
7735                ],
7736            }),
7737            Err(Error::Validate {
7738                err,
7739                ..
7740            }) if &err ==
7741                "Strong dependency cycles were found. Break the cycle by removing a \
7742                dependency or marking an offer as weak. Cycles: {{#a -> self -> #dict -> #a}}"
7743        ),
7744    }}
7745
7746    // Tests that offering and exposing service capabilities to the same target and target name is
7747    // allowed.
7748    test_validate_cml! {
7749        test_cml_aggregate_expose(
7750            json!({
7751                "expose": [
7752                    {
7753                        "service": "fuchsia.foo.Bar",
7754                        "from": ["#a", "#b"],
7755                    },
7756                ],
7757                "children": [
7758                    {
7759                        "name": "a",
7760                        "url": "fuchsia-pkg://fuchsia.com/a#meta/a.cm",
7761                    },
7762                    {
7763                        "name": "b",
7764                        "url": "fuchsia-pkg://fuchsia.com/b#meta/b.cm",
7765                    },
7766                ],
7767            }),
7768            Ok(())
7769        ),
7770        test_cml_aggregate_offer(
7771            json!({
7772                "offer": [
7773                    {
7774                        "service": "fuchsia.foo.Bar",
7775                        "from": ["#a", "#b"],
7776                        "to": "#target",
7777                    },
7778                ],
7779                "children": [
7780                    {
7781                        "name": "a",
7782                        "url": "fuchsia-pkg://fuchsia.com/a#meta/a.cm",
7783                    },
7784                    {
7785                        "name": "b",
7786                        "url": "fuchsia-pkg://fuchsia.com/b#meta/b.cm",
7787                    },
7788                    {
7789                        "name": "target",
7790                        "url": "fuchsia-pkg://fuchsia.com/target#meta/target.cm",
7791                    },
7792                ],
7793            }),
7794            Ok(())
7795        ),
7796    }
7797
7798    use crate::translate::test_util::must_parse_cml;
7799    use crate::translate::{compile, CompileOptions};
7800
7801    #[test]
7802    fn test_cml_use_bad_config_from_self() {
7803        let input = must_parse_cml!({
7804        "use": [
7805            {
7806                "config": "fuchsia.config.MyConfig",
7807                "key": "my_config",
7808                "type": "bool",
7809                "from": "self",
7810            },
7811        ],
7812        });
7813
7814        let options = CompileOptions::new();
7815        assert_matches!(compile(&input, options), Err(Error::Validate { .. }));
7816    }
7817
7818    // Tests for config capabilities
7819    test_validate_cml! {
7820        a_test_cml_use_config(
7821        json!({"use": [
7822            {
7823                "config": "fuchsia.config.MyConfig",
7824                "key": "my_config",
7825                "type": "bool",
7826            },
7827        ],}),
7828        Ok(())
7829        ),
7830        test_cml_use_config_good_vector(
7831        json!({"use": [
7832            {
7833                "config": "fuchsia.config.MyConfig",
7834                "key": "my_config",
7835                "type": "vector",
7836                "element": { "type": "bool"},
7837                "max_count": 1,
7838            },
7839        ],}),
7840        Ok(())
7841        ),
7842        test_cml_use_config_bad_vector(
7843        json!({"use": [
7844            {
7845                "config": "fuchsia.config.MyConfig",
7846                "key": "my_config",
7847                "type": "vector",
7848                "element": { "type": "bool"},
7849                // Missing max count.
7850            },
7851        ],}),
7852        Err(Error::Validate {err,  .. })
7853        if &err == "Config 'fuchsia.config.MyConfig' is type Vector but is missing field 'max_count'"
7854        ),
7855        test_cml_use_config_bad_string(
7856        json!({"use": [
7857            {
7858                "config": "fuchsia.config.MyConfig",
7859                "key": "my_config",
7860                "type": "string",
7861                // Missing max size.
7862            },
7863        ],}),
7864        Err(Error::Validate { err, .. })
7865        if &err == "Config 'fuchsia.config.MyConfig' is type String but is missing field 'max_size'"
7866        ),
7867
7868        test_cml_optional_use_no_config(
7869        json!({"use": [
7870            {
7871                "config": "fuchsia.config.MyConfig",
7872                "key": "my_config",
7873                "type": "bool",
7874                "availability": "optional",
7875            },
7876        ],}),
7877        Err(Error::Validate {err, ..})
7878        if &err == "Optionally using a config capability without a default requires a matching 'config' section."
7879        ),
7880        test_cml_transitional_use_no_config(
7881        json!({"use": [
7882            {
7883                "config": "fuchsia.config.MyConfig",
7884                "key": "my_config",
7885                "type": "bool",
7886                "availability": "transitional",
7887            },
7888        ],}),
7889        Err(Error::Validate {err, ..})
7890        if &err == "Optionally using a config capability without a default requires a matching 'config' section."
7891        ),
7892        test_cml_optional_use_bad_type(
7893        json!({"use": [
7894            {
7895                "config": "fuchsia.config.MyConfig",
7896                "key": "my_config",
7897                "type": "bool",
7898                "availability": "optional",
7899            },
7900        ],
7901        "config": {
7902            "my_config": { "type": "uint8"}
7903        }}),
7904        Err(Error::Validate {err, ..})
7905        if &err == "Use and config block differ on type for key 'my_config'"
7906        ),
7907
7908        test_config_required_with_default(
7909        json!({"use": [
7910            {
7911                "config": "fuchsia.config.MyConfig",
7912                "key": "my_config",
7913                "type": "bool",
7914                "default": "true",
7915            },
7916        ]}),
7917        Err(Error::Validate {err, ..})
7918        if &err == "Config 'fuchsia.config.MyConfig' is required and has a default value"
7919        ),
7920
7921        test_cml_optional_use_good(
7922        json!({"use": [
7923            {
7924                "config": "fuchsia.config.MyConfig",
7925                "key": "my_config",
7926                "type": "bool",
7927                "availability": "optional",
7928            },
7929        ],
7930        "config": {
7931            "my_config": { "type": "bool"},
7932        }
7933    }),
7934    Ok(())
7935        ),
7936    test_cml_use_two_types_bad(
7937        json!({"use": [
7938            {
7939                "protocol": "fuchsia.protocol.MyProtocol",
7940                "service": "fuchsia.service.MyService",
7941            },
7942        ],
7943    }),
7944        Err(Error::Validate {err, ..})
7945        if &err == "use declaration has multiple capability types defined: [\"service\", \"protocol\"]"
7946        ),
7947    test_cml_offer_two_types_bad(
7948        json!({"offer": [
7949            {
7950                "protocol": "fuchsia.protocol.MyProtocol",
7951                "service": "fuchsia.service.MyService",
7952                "from": "self",
7953                "to" : "#child",
7954            },
7955        ],
7956    }),
7957        Err(Error::Validate {err, ..})
7958        if &err == "offer declaration has multiple capability types defined: [\"service\", \"protocol\"]"
7959        ),
7960    test_cml_expose_two_types_bad(
7961        json!({"expose": [
7962            {
7963                "protocol": "fuchsia.protocol.MyProtocol",
7964                "service": "fuchsia.service.MyService",
7965                "from" : "self",
7966            },
7967        ],
7968    }),
7969        Err(Error::Validate {err, ..})
7970        if &err == "expose declaration has multiple capability types defined: [\"service\", \"protocol\"]"
7971        ),
7972    }
7973}