Skip to main content

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::types::capability::{Capability, CapabilityFromRef, ContextCapability};
7use crate::types::capability_id::CapabilityId;
8use crate::types::child::{Child, ContextChild};
9use crate::types::collection::{Collection, ContextCollection};
10use crate::types::document::{Document, DocumentContext};
11use crate::types::environment::{
12    ContextEnvironment, Environment, EnvironmentExtends, RegistrationRef,
13};
14use crate::types::expose::{ContextExpose, Expose, ExposeFromRef, ExposeToRef};
15use crate::types::offer::{
16    ContextOffer, Offer, OfferFromRef, OfferToAllCapability, OfferToRef, TargetAvailability,
17    offer_to_all_would_duplicate, offer_to_all_would_duplicate_context,
18};
19use crate::types::right::Rights;
20use crate::types::r#use::{ContextUse, Use, UseFromRef};
21use crate::{
22    AnyRef, Availability, CapabilityClause, ConfigKey, ConfigType, ConfigValueType,
23    ContextCapabilityClause, ContextSpanned, DependencyType, DictionaryRef, Error, EventScope,
24    FromClause, FromClauseContext, OneOrMany, Program, RootDictionaryRef, SourceAvailability,
25};
26use cm_types::{BorrowedName, IterablePath, Name};
27use std::collections::{BTreeMap, HashMap, HashSet};
28use std::hash::Hash;
29use std::path::Path;
30use std::{fmt, iter};
31
32use std::path::PathBuf;
33use std::sync::Arc;
34
35#[derive(Default, Clone)]
36pub struct CapabilityRequirements<'a> {
37    pub must_offer: &'a [OfferToAllCapability<'a>],
38    pub must_use: &'a [MustUseRequirement<'a>],
39}
40
41#[derive(PartialEq)]
42pub enum MustUseRequirement<'a> {
43    Protocol(&'a str),
44}
45
46impl<'a> MustUseRequirement<'a> {
47    fn name(&self) -> &str {
48        match self {
49            MustUseRequirement::Protocol(name) => name,
50        }
51    }
52}
53
54macro_rules! val {
55    ($field:expr) => {
56        $field.as_ref().map(|s| &s.value)
57    };
58}
59
60/// Validates a given cml.
61pub(crate) fn validate_cml(
62    document: &Document,
63    file: Option<&Path>,
64    features: &FeatureSet,
65    capability_requirements: &CapabilityRequirements<'_>,
66) -> Result<(), Error> {
67    let mut ctx = ValidationContext::new(&document, features, capability_requirements);
68    let mut res = ctx.validate();
69    if let Err(Error::Validate { filename, .. }) = &mut res {
70        if let Some(file) = file {
71            *filename = Some(file.to_string_lossy().into_owned());
72        }
73    }
74    res
75}
76
77#[allow(dead_code)]
78pub(crate) fn validate_cml_context(
79    document: &DocumentContext,
80    features: &FeatureSet,
81    capability_requirements: &CapabilityRequirements<'_>,
82) -> Result<(), Error> {
83    let mut ctx = ValidationContextV2::new(&document, features, capability_requirements);
84    ctx.validate()
85}
86
87fn offer_can_have_dependency_no_span(offer: &Offer) -> bool {
88    offer.directory.is_some()
89        || offer.protocol.is_some()
90        || offer.service.is_some()
91        || offer.dictionary.is_some()
92}
93
94fn offer_can_have_dependency(offer: &ContextOffer) -> bool {
95    offer.directory.is_some()
96        || offer.protocol.is_some()
97        || offer.service.is_some()
98        || offer.dictionary.is_some()
99}
100
101fn offer_dependency_no_span(offer: &Offer) -> DependencyType {
102    offer.dependency.clone().unwrap_or(DependencyType::Strong)
103}
104
105fn offer_dependency(offer: &ContextOffer) -> DependencyType {
106    match offer.dependency.clone() {
107        Some(cs_dep) => cs_dep.value,
108        None => DependencyType::Strong,
109    }
110}
111
112type ConflictInfo<'a> = (CapabilityId<'a>, Arc<PathBuf>);
113
114struct ValidationContextV2<'a> {
115    document: &'a DocumentContext,
116    features: &'a FeatureSet,
117    _capability_requirements: &'a CapabilityRequirements<'a>,
118    all_children: HashSet<Name>,
119    all_collections: HashSet<Name>,
120    all_resolvers: HashSet<Name>,
121    all_runners: HashSet<Name>,
122    all_storages: HashMap<Name, &'a CapabilityFromRef>,
123    all_capability_names: HashSet<Name>,
124    all_dictionaries: HashMap<Name, &'a ContextCapability>,
125    all_directories: HashSet<Name>,
126    all_protocols: HashSet<Name>,
127    all_configs: HashSet<Name>,
128    all_services: HashSet<Name>,
129}
130
131impl<'a> ValidationContextV2<'a> {
132    fn new(
133        document: &'a DocumentContext,
134        features: &'a FeatureSet,
135        _capability_requirements: &'a CapabilityRequirements<'a>,
136    ) -> Self {
137        let all_children = document.all_children_names();
138        let all_collections = document.all_collection_names();
139        let all_storages = document.all_storage_with_sources();
140        let all_capability_names = document.all_capability_names();
141        let all_dictionaries = document.all_dictionaries();
142        let all_configs = document.all_config_names();
143        let all_services = document
144            .capabilities
145            .as_ref()
146            .map(|caps| {
147                caps.iter()
148                    .filter(|c| c.value.service.is_some())
149                    .flat_map(|c| c.value.names())
150                    .map(|n| n.value)
151                    .collect()
152            })
153            .unwrap_or_default();
154
155        let all_directories = document
156            .capabilities
157            .as_ref()
158            .map(|caps| {
159                caps.iter()
160                    .filter(|c| c.value.directory.is_some())
161                    .flat_map(|c| c.value.names())
162                    .map(|n| n.value)
163                    .collect()
164            })
165            .unwrap_or_default();
166
167        let all_runners = document
168            .capabilities
169            .as_ref()
170            .map(|caps| {
171                caps.iter()
172                    .filter(|c| c.value.runner.is_some())
173                    .flat_map(|c| c.value.names())
174                    .map(|n| n.value)
175                    .collect()
176            })
177            .unwrap_or_default();
178
179        let all_resolvers = document
180            .capabilities
181            .as_ref()
182            .map(|caps| {
183                caps.iter()
184                    .filter(|c| c.value.resolver.is_some())
185                    .flat_map(|c| c.value.names())
186                    .map(|n| n.value)
187                    .collect()
188            })
189            .unwrap_or_default();
190
191        let all_protocols = document
192            .capabilities
193            .as_ref()
194            .map(|caps| {
195                caps.iter()
196                    .filter(|c| c.value.protocol.is_some())
197                    .flat_map(|c| c.value.names())
198                    .map(|n| n.value)
199                    .collect()
200            })
201            .unwrap_or_default();
202
203        ValidationContextV2 {
204            document,
205            features,
206            _capability_requirements,
207            all_children,
208            all_collections,
209            all_resolvers,
210            all_runners,
211            all_storages,
212            all_capability_names,
213            all_dictionaries,
214            all_directories,
215            all_protocols,
216            all_configs,
217            all_services,
218        }
219    }
220
221    fn validate(&mut self) -> Result<(), Error> {
222        if let Some(children) = self.document.children.as_ref() {
223            for child in children {
224                self.validate_child(&child)?;
225            }
226        }
227
228        if let Some(collections) = self.document.collections.as_ref() {
229            for collection in collections {
230                self.validate_collection(&collection)?;
231            }
232        }
233
234        if let Some(capabilities) = self.document.capabilities.as_ref() {
235            let mut used_ids = HashMap::new();
236            for capability in capabilities.iter() {
237                self.validate_capability(&capability, &mut used_ids)?;
238            }
239        }
240
241        if let Some(uses) = self.document.r#use.as_ref() {
242            let mut used_ids = HashMap::new();
243            for use_ in uses.iter() {
244                self.validate_use(&use_, &mut used_ids)?;
245            }
246        }
247
248        if let Some(exposes) = self.document.expose.as_ref() {
249            let mut used_ids = HashMap::new();
250            let mut exposed_to_framework_ids = HashMap::new();
251            for expose in exposes.iter() {
252                self.validate_expose(&expose, &mut used_ids, &mut exposed_to_framework_ids)?;
253            }
254        }
255
256        if let Some(offers) = self.document.offer.as_ref() {
257            let mut problem_protocols: Vec<ConflictInfo<'a>> = Vec::new();
258            let mut problem_dictionaries: Vec<ConflictInfo<'a>> = Vec::new();
259            let mut offered_ids: HashSet<CapabilityId<'a>> = HashSet::new();
260
261            offers
262                .iter()
263                .filter(|o_span| matches!(o_span.value.to.value, OneOrMany::One(OfferToRef::All)))
264                .try_for_each(|offer_wrapper| -> Result<(), Error> {
265                    let offer = &offer_wrapper.value;
266                    let mut process_cap = |field_is_some: bool,
267                                           conflict_list: &mut Vec<ConflictInfo<'a>>|
268                     -> Result<(), Error> {
269                        if field_is_some {
270                            for (cap_id, cap_origin) in
271                                CapabilityId::from_context_offer(offer_wrapper)?
272                            {
273                                if !offered_ids.insert(cap_id.clone()) {
274                                    conflict_list.push((cap_id, cap_origin));
275                                }
276                            }
277                        }
278                        Ok(())
279                    };
280
281                    process_cap(offer.protocol.is_some(), &mut problem_protocols)?;
282                    process_cap(offer.dictionary.is_some(), &mut problem_dictionaries)?;
283                    Ok(())
284                })?;
285
286            if !problem_protocols.is_empty() {
287                return Err(Error::validate_contexts(
288                    format!(
289                        r#"{} {:?} offered to "all" multiple times"#,
290                        "Protocol(s)",
291                        problem_protocols.iter().map(|(p, _o)| format!("{p}")).collect::<Vec<_>>()
292                    ),
293                    problem_protocols.iter().map(|(_p, o)| o.clone()).collect::<Vec<_>>(),
294                ));
295            }
296
297            if !problem_dictionaries.is_empty() {
298                return Err(Error::validate_contexts(
299                    format!(
300                        r#"{} {:?} offered to "all" multiple times"#,
301                        "Dictionary(s)",
302                        problem_dictionaries
303                            .iter()
304                            .map(|(p, _)| format!("{p}"))
305                            .collect::<Vec<_>>()
306                    ),
307                    problem_dictionaries.iter().map(|(_p, o)| o.clone()).collect::<Vec<_>>(),
308                ));
309            }
310
311            let offered_to_all = offers
312                .iter()
313                .filter(|o| matches!(o.value.to.value, OneOrMany::One(OfferToRef::All)))
314                .filter(|o| o.value.protocol.is_some() || o.value.dictionary.is_some())
315                .collect::<Vec<&ContextSpanned<ContextOffer>>>();
316
317            let mut offered_ids = HashMap::new();
318            for offer in offers.iter() {
319                self.validate_offer(&offer, &mut offered_ids, &offered_to_all)?;
320            }
321        }
322
323        if let Some(environments) = self.document.environments.as_ref() {
324            for environment in environments {
325                self.validate_environment(&environment)?;
326            }
327        }
328
329        self.validate_facets()?;
330
331        self.validate_config()?;
332
333        Ok(())
334    }
335
336    fn validate_child(
337        &mut self,
338        child_wrapper: &'a ContextSpanned<ContextChild>,
339    ) -> Result<(), Error> {
340        let child = &child_wrapper.value;
341
342        if let Some(resource) = child.url.value.resource() {
343            if resource.ends_with(".cml") {
344                return Err(Error::validate_context(
345                    format!(
346                        "child URL ends in .cml instead of .cm, \
347which is almost certainly a mistake: {}",
348                        child.url.value
349                    ),
350                    Some(child.url.origin.clone()),
351                ));
352            }
353        }
354
355        Ok(())
356    }
357
358    fn validate_capability(
359        &mut self,
360        capability_wrapper: &'a ContextSpanned<ContextCapability>,
361        used_ids: &mut HashMap<String, Arc<PathBuf>>,
362    ) -> Result<(), Error> {
363        let capability = &capability_wrapper.value;
364
365        if let Some(cs_directory) = &capability.directory {
366            if capability.path.is_none() {
367                return Err(Error::validate_context(
368                    "\"path\" should be present with \"directory\"",
369                    Some(cs_directory.origin.clone()),
370                ));
371            }
372
373            if capability.rights.is_none() {
374                return Err(Error::validate_context(
375                    "\"rights\" should be present with \"directory\"",
376                    Some(cs_directory.origin.clone()),
377                ));
378            }
379        }
380
381        if let Some(cs_storage) = capability.storage.as_ref() {
382            if capability.from.is_none() {
383                return Err(Error::validate_context(
384                    "\"from\" should be present with \"storage\"",
385                    Some(cs_storage.origin.clone()),
386                ));
387            }
388            if let Some(cs_path) = &capability.path {
389                return Err(Error::validate_context(
390                    "\"path\" cannot be present with \"storage\", use \"backing_dir\"",
391                    Some(cs_path.origin.clone()),
392                ));
393            }
394            if capability.backing_dir.is_none() {
395                return Err(Error::validate_context(
396                    "\"backing_dir\" should be present with \"storage\"",
397                    Some(cs_storage.origin.clone()),
398                ));
399            }
400            if capability.storage_id.is_none() {
401                return Err(Error::validate_context(
402                    "\"storage_id\" should be present with \"storage\"",
403                    Some(cs_storage.origin.clone()),
404                ));
405            }
406        }
407
408        if let Some(cs_runner) = &capability.runner {
409            if let Some(cs_from) = &capability.from {
410                return Err(Error::validate_context(
411                    "\"from\" should not be present with \"runner\"",
412                    Some(cs_from.origin.clone()),
413                ));
414            }
415
416            if capability.path.is_none() {
417                return Err(Error::validate_context(
418                    "\"path\" should be present with \"runner\"",
419                    Some(cs_runner.origin.clone()),
420                ));
421            }
422        }
423
424        if let Some(cs_resolver) = &capability.resolver {
425            if let Some(cs_from) = &capability.from {
426                return Err(Error::validate_context(
427                    "\"from\" should not be present with \"resolver\"",
428                    Some(cs_from.origin.clone()),
429                ));
430            }
431
432            if capability.path.is_none() {
433                return Err(Error::validate_context(
434                    "\"path\" should be present with \"resolver\"",
435                    Some(cs_resolver.origin.clone()),
436                ));
437            }
438        }
439
440        if capability.dictionary.as_ref().is_some() && capability.path.is_some() {
441            self.features.check(Feature::DynamicDictionaries)?;
442        }
443        if capability.delivery.is_some() {
444            self.features.check(Feature::DeliveryType)?;
445        }
446        if let Some(from) = capability.from.as_ref() {
447            self.validate_component_child_ref(
448                "\"capabilities\" source",
449                &AnyRef::from(&from.value),
450                Some(&capability_wrapper.origin),
451            )?;
452        }
453
454        // Disallow multiple capability ids of the same name.
455        let capability_ids = CapabilityId::from_context_capability(capability_wrapper)?;
456        for (capability_id, cap_origin) in capability_ids {
457            if let Some(conflict_origin) =
458                used_ids.insert(capability_id.to_string(), cap_origin.clone())
459            {
460                return Err(Error::validate_contexts(
461                    format!("\"{}\" is a duplicate \"capability\" name", capability_id,),
462                    vec![cap_origin, conflict_origin],
463                ));
464            }
465        }
466
467        Ok(())
468    }
469
470    fn validate_use(
471        &mut self,
472        use_wrapper: &'a ContextSpanned<ContextUse>,
473        used_ids: &mut HashMap<String, (CapabilityId<'a>, Arc<PathBuf>)>,
474    ) -> Result<(), Error> {
475        use_wrapper.capability_type(Some(use_wrapper.origin.clone()))?;
476        let use_ = &use_wrapper.value;
477
478        for checker in [
479            self.service_from_self_checker(use_),
480            self.protocol_from_self_checker(use_),
481            self.directory_from_self_checker(use_),
482            self.config_from_self_checker(use_),
483        ] {
484            checker.validate("used")?;
485        }
486
487        if val!(&use_.from) == Some(&UseFromRef::Debug) && val!(&use_.protocol).is_none() {
488            return Err(Error::validate_context(
489                "only \"protocol\" supports source from \"debug\"",
490                use_.from.as_ref().map(|s| s.origin.clone()),
491            ));
492        }
493
494        if use_.event_stream.is_some() {
495            if let Some(avail) = &use_.availability {
496                return Err(Error::validate_context(
497                    "\"availability\" cannot be used with \"event_stream\"",
498                    Some(avail.origin.clone()),
499                ));
500            }
501            if val!(&use_.from) == Some(&UseFromRef::Self_) {
502                return Err(Error::validate_context(
503                    "\"from: self\" cannot be used with \"event_stream\"",
504                    use_.from.as_ref().map(|s| s.origin.clone()),
505                ));
506            }
507        } else {
508            // event_stream is NONE.
509            if let Some(filter) = &use_.filter {
510                return Err(Error::validate_context(
511                    "\"filter\" can only be used with \"event_stream\"",
512                    Some(filter.origin.clone()),
513                ));
514            }
515        }
516
517        if use_.storage.is_some() {
518            if let Some(from) = &use_.from {
519                return Err(Error::validate_context(
520                    "\"from\" cannot be used with \"storage\"",
521                    Some(from.origin.clone()),
522                ));
523            }
524        }
525
526        if use_.runner.is_some() {
527            if let Some(avail) = &use_.availability {
528                return Err(Error::validate_context(
529                    "\"availability\" cannot be used with \"runner\"",
530                    Some(avail.origin.clone()),
531                ));
532            }
533            if val!(&use_.from) == Some(&UseFromRef::Self_) {
534                return Err(Error::validate_context(
535                    "\"from: self\" cannot be used with \"runner\"",
536                    use_.from.as_ref().map(|s| s.origin.clone()),
537                ));
538            }
539        }
540
541        if let Some(avail) = &use_.availability {
542            if avail.value == Availability::SameAsTarget {
543                return Err(Error::validate_context(
544                    "\"availability: same_as_target\" cannot be used with use declarations",
545                    Some(avail.origin.clone()),
546                ));
547            }
548        }
549
550        if use_.dictionary.is_some() {
551            self.features.check(Feature::UseDictionaries)?;
552        }
553        if let Some(ContextSpanned { value: UseFromRef::Dictionary(_), origin: _ }) =
554            use_.from.as_ref()
555        {
556            if let Some(storage) = &use_.storage {
557                return Err(Error::validate_context(
558                    "Dictionaries do not support \"storage\" capabilities",
559                    Some(storage.origin.clone()),
560                ));
561            }
562            if let Some(event_stream) = &use_.event_stream {
563                return Err(Error::validate_context(
564                    "Dictionaries do not support \"event_stream\" capabilities",
565                    Some(event_stream.origin.clone()),
566                ));
567            }
568        }
569
570        if let Some(config) = &use_.config {
571            if use_.key.is_none() {
572                return Err(Error::validate_context(
573                    format!("Config '{}' missing field 'key'", config.value),
574                    Some(config.origin.clone()),
575                ));
576            }
577            let _ = use_config_to_value_type_context(use_)?;
578
579            let availability = val!(&use_.availability).cloned().unwrap_or(Availability::Required);
580            if availability == Availability::Required {
581                if let Some(default) = &use_.config_default {
582                    return Err(Error::validate_context(
583                        format!("Config '{}' is required and has a default value", config.value),
584                        Some(default.origin.clone()),
585                    ));
586                }
587            }
588        }
589
590        if let Some(handle) = &use_.numbered_handle {
591            if use_.protocol.is_some() {
592                if let Some(path) = &use_.path {
593                    return Err(Error::validate_context(
594                        "`path` and `numbered_handle` are incompatible",
595                        Some(path.origin.clone()),
596                    ));
597                }
598            } else {
599                return Err(Error::validate_context(
600                    "`numbered_handle` is only supported for `use protocol`",
601                    Some(handle.origin.clone()),
602                ));
603            }
604        }
605
606        let capability_ids_with_origins = CapabilityId::from_context_use(use_wrapper)?;
607        for (capability_id, origin) in capability_ids_with_origins {
608            if let Some((conflicting_id, conflicting_origin)) =
609                used_ids.insert(capability_id.to_string(), (capability_id.clone(), origin.clone()))
610            {
611                if !matches!(
612                    (&capability_id, &conflicting_id),
613                    (CapabilityId::UsedDictionary(_), CapabilityId::UsedDictionary(_))
614                ) {
615                    return Err(Error::validate_contexts(
616                        format!(
617                            "\"{}\" is a duplicate \"use\" target {}",
618                            capability_id,
619                            capability_id.type_str()
620                        ),
621                        vec![origin, conflicting_origin],
622                    ));
623                }
624            }
625
626            let dir = capability_id.get_dir_path();
627
628            // Capability paths must not conflict with `/pkg`, or namespace generation might fail
629            let pkg_path = cm_types::NamespacePath::new("/pkg").unwrap();
630            if let Some(ref dir) = dir {
631                if dir.has_prefix(&pkg_path) {
632                    return Err(Error::validate_context(
633                        format!(
634                            "{} \"{}\" conflicts with the protected path \"/pkg\", please use this capability with a different path",
635                            capability_id.type_str(),
636                            capability_id,
637                        ),
638                        Some(origin),
639                    ));
640                }
641            }
642
643            // Validate that paths-based capabilities (service, directory, protocol)
644            // are not prefixes of each other.
645            for (_, (used_id, origin)) in used_ids.iter() {
646                if capability_id == *used_id {
647                    continue;
648                }
649                let Some(ref path_b) = capability_id.get_target_path() else {
650                    continue;
651                };
652                let Some(path_a) = used_id.get_target_path() else {
653                    continue;
654                };
655                #[derive(Debug, Clone, Copy)]
656                enum NodeType {
657                    Service,
658                    Directory,
659                    // This variant is never constructed if we're at an API version before "use
660                    // dictionary" was added.
661                    #[allow(unused)]
662                    Dictionary,
663                }
664                fn capability_id_to_type(id: &CapabilityId<'_>) -> Option<NodeType> {
665                    match id {
666                        CapabilityId::UsedConfiguration(_) => None,
667                        #[cfg(fuchsia_api_level_at_least = "30")]
668                        CapabilityId::UsedDictionary(_) => Some(NodeType::Dictionary),
669                        CapabilityId::UsedDirectory(_) => Some(NodeType::Directory),
670                        CapabilityId::UsedEventStream(_) => Some(NodeType::Service),
671                        CapabilityId::UsedProtocol(_) => Some(NodeType::Service),
672                        #[cfg(fuchsia_api_level_at_least = "HEAD")]
673                        CapabilityId::UsedRunner(_) => None,
674                        CapabilityId::UsedService(_) => Some(NodeType::Directory),
675                        CapabilityId::UsedStorage(_) => Some(NodeType::Directory),
676                        _ => None,
677                    }
678                }
679                let Some(type_a) = capability_id_to_type(&used_id) else {
680                    continue;
681                };
682                let Some(type_b) = capability_id_to_type(&capability_id) else {
683                    continue;
684                };
685                let mut conflicts = false;
686                match (type_a, type_b) {
687                    (NodeType::Service, NodeType::Service)
688                    | (NodeType::Directory, NodeType::Service)
689                    | (NodeType::Service, NodeType::Directory)
690                    | (NodeType::Directory, NodeType::Directory) => {
691                        if path_a.has_prefix(&path_b) || path_b.has_prefix(&path_a) {
692                            conflicts = true;
693                        }
694                    }
695                    (NodeType::Dictionary, NodeType::Service)
696                    | (NodeType::Dictionary, NodeType::Directory) => {
697                        if path_a.has_prefix(&path_b) {
698                            conflicts = true;
699                        }
700                    }
701                    (NodeType::Service, NodeType::Dictionary)
702                    | (NodeType::Directory, NodeType::Dictionary) => {
703                        if path_b.has_prefix(&path_a) {
704                            conflicts = true;
705                        }
706                    }
707                    (NodeType::Dictionary, NodeType::Dictionary) => {
708                        // All combinations of two dictionaries are valid.
709                    }
710                }
711                if conflicts {
712                    return Err(Error::validate_contexts(
713                        format!(
714                            "{} \"{}\" is a prefix of \"use\" target {} \"{}\"",
715                            used_id.type_str(),
716                            used_id,
717                            capability_id.type_str(),
718                            capability_id,
719                        ),
720                        vec![origin.clone()],
721                    ));
722                }
723            }
724        }
725
726        if let Some(dir) = &use_.directory {
727            match &use_.rights {
728                Some(rights) => {
729                    self.validate_directory_rights(&rights.value, Some(&rights.origin))?
730                }
731                None => {
732                    return Err(Error::validate_contexts(
733                        "This use statement requires a `rights` field. Refer to: https://fuchsia.dev/go/components/directory#consumer.",
734                        vec![dir.origin.clone()],
735                    ));
736                }
737            };
738        }
739
740        Ok(())
741    }
742
743    fn validate_expose(
744        &self,
745        expose_wrapper: &'a ContextSpanned<ContextExpose>,
746        used_ids: &mut HashMap<String, Arc<PathBuf>>,
747        exposed_to_framework_ids: &mut HashMap<String, Arc<PathBuf>>,
748    ) -> Result<(), Error> {
749        let expose = &expose_wrapper.value;
750
751        expose.capability_type(Some(expose_wrapper.origin.clone()))?;
752
753        for checker in [
754            self.service_from_self_checker(expose),
755            self.protocol_from_self_checker(expose),
756            self.directory_from_self_checker(expose),
757            self.runner_from_self_checker(expose),
758            self.resolver_from_self_checker(expose),
759            self.dictionary_from_self_checker(expose),
760            self.config_from_self_checker(expose),
761        ] {
762            checker.validate("exposed")?;
763        }
764
765        // Ensure directory rights are valid.
766        if let Some(_) = expose.directory.as_ref() {
767            if expose.from.value.iter().any(|r| *r == ExposeFromRef::Self_)
768                || expose.rights.is_some()
769            {
770                if let Some(rights) = expose.rights.as_ref() {
771                    self.validate_directory_rights(&rights.value, Some(&rights.origin))?;
772                }
773            }
774
775            // Exposing a subdirectory makes sense for routing but when exposing to framework,
776            // the subdir should be exposed directly.
777            if let Some(e2) = &expose.to {
778                if e2.value == ExposeToRef::Framework
779                    && let Some(expose_subdir) = &expose.subdir
780                {
781                    return Err(Error::validate_context(
782                        "`subdir` is not supported for expose to framework. Directly expose the subdirectory instead.",
783                        Some(expose_subdir.origin.clone()),
784                    ));
785                }
786            }
787        }
788
789        if let Some(event_stream) = &expose.event_stream {
790            if event_stream.value.iter().len() > 1 && expose.r#as.is_some() {
791                return Err(Error::validate_context(
792                    format!("as cannot be used with multiple event streams"),
793                    Some(expose.r#as.clone().unwrap().origin),
794                ));
795            }
796            if let Some(e2) = &expose.to
797                && e2.value == ExposeToRef::Framework
798            {
799                return Err(Error::validate_context(
800                    format!("cannot expose an event_stream to framework"),
801                    Some(event_stream.origin.clone()),
802                ));
803            }
804            for from in expose.from.value.iter() {
805                if from == &ExposeFromRef::Self_ {
806                    return Err(Error::validate_context(
807                        format!("Cannot expose event_streams from self"),
808                        Some(event_stream.origin.clone()),
809                    ));
810                }
811            }
812            if let Some(scopes) = &expose.scope {
813                for scope in &scopes.value {
814                    match scope {
815                        EventScope::Named(name) => {
816                            if !self.all_children.contains::<BorrowedName>(name)
817                                && !self.all_collections.contains::<BorrowedName>(name)
818                            {
819                                return Err(Error::validate_context(
820                                    format!(
821                                        "event_stream scope {} did not match a component or collection in this .cml file.",
822                                        name.as_str()
823                                    ),
824                                    Some(scopes.origin.clone()),
825                                ));
826                            }
827                        }
828                    }
829                }
830            }
831        }
832
833        for ref_ in expose.from.value.iter() {
834            if let ExposeFromRef::Dictionary(d) = ref_ {
835                if expose.event_stream.is_some() {
836                    return Err(Error::validate_context(
837                        "Dictionaries do not support \"event_stream\" capabilities",
838                        Some(expose.event_stream.clone().unwrap().origin),
839                    ));
840                }
841                match &d.root {
842                    RootDictionaryRef::Self_ | RootDictionaryRef::Named(_) => {}
843                    RootDictionaryRef::Parent => {
844                        return Err(Error::validate_context(
845                            "`expose` dictionary path must begin with `self` or `#<child-name>`",
846                            Some(expose.from.origin.clone()),
847                        ));
848                    }
849                }
850            }
851        }
852
853        // Ensure we haven't already exposed an entity of the same name.
854        let target_cap_ids_with_origin = CapabilityId::from_context_expose(expose_wrapper)?;
855        for (capability_id, cap_origin) in target_cap_ids_with_origin {
856            let mut ids = &mut *used_ids;
857            if let Some(e2) = &expose.to
858                && e2.value == ExposeToRef::Framework
859            {
860                ids = &mut *exposed_to_framework_ids;
861            }
862            if let Some(conflict_origin) = ids.insert(capability_id.to_string(), cap_origin.clone())
863            {
864                if let CapabilityId::Service(_) = capability_id {
865                    // Services may have duplicates (aggregation).
866                } else {
867                    let expose_print = match &expose.to {
868                        Some(expose_to) => &expose_to.value,
869                        None => &ExposeToRef::Parent,
870                    };
871                    return Err(Error::validate_contexts(
872                        format!(
873                            "\"{}\" is a duplicate \"expose\" target capability for \"{}\"",
874                            capability_id, expose_print
875                        ),
876                        vec![cap_origin, conflict_origin],
877                    ));
878                }
879            }
880        }
881
882        // Validate `from` (done last because this validation depends on the capability type, which
883        // must be validated first)
884        self.validate_from_clause(
885            "expose",
886            expose,
887            &expose.source_availability.as_ref().map(|s| s.value.clone()),
888            &expose.availability.as_ref().map(|s| s.value.clone()),
889            expose.from.origin.clone(),
890        )?;
891
892        Ok(())
893    }
894
895    fn validate_offer(
896        &mut self,
897        offer_wrapper: &'a ContextSpanned<ContextOffer>,
898        used_ids: &mut HashMap<Name, HashMap<String, Arc<PathBuf>>>,
899        protocols_offered_to_all: &[&'a ContextSpanned<ContextOffer>],
900    ) -> Result<(), Error> {
901        let offer = &offer_wrapper.value;
902        offer.capability_type(Some(offer_wrapper.origin.clone()))?;
903
904        for checker in [
905            self.service_from_self_checker(offer),
906            self.protocol_from_self_checker(offer),
907            self.directory_from_self_checker(offer),
908            self.storage_from_self_checker(offer),
909            self.runner_from_self_checker(offer),
910            self.resolver_from_self_checker(offer),
911            self.dictionary_from_self_checker(offer),
912            self.config_from_self_checker(offer),
913        ] {
914            checker.validate("offered")?;
915        }
916
917        let from_wrapper = &offer.from;
918        let from_one_or_many = &from_wrapper.value;
919        let from_self = self.from_self(from_one_or_many);
920
921        if let Some(stream_span) = offer.event_stream.as_ref() {
922            if stream_span.value.iter().len() > 1 {
923                if let Some(as_span) = offer.r#as.as_ref() {
924                    return Err(Error::validate_context(
925                        "as cannot be used with multiple events",
926                        Some(as_span.origin.clone()),
927                    ));
928                }
929            }
930
931            if from_self {
932                return Err(Error::validate_context(
933                    "cannot offer an event_stream from self",
934                    Some(from_wrapper.origin.clone()),
935                ));
936            }
937        }
938
939        if offer.directory.as_ref().is_some() {
940            if from_self || offer.rights.is_some() {
941                if let Some(rights_span) = offer.rights.as_ref() {
942                    self.validate_directory_rights(&rights_span.value, Some(&rights_span.origin))?;
943                }
944            }
945        }
946
947        if let Some(storages) = offer.storage.as_ref() {
948            for storage in &storages.value {
949                if offer.from.value.iter().any(|r| r.is_named()) {
950                    return Err(Error::validate_contexts(
951                        format!(
952                            "Storage \"{}\" is offered from a child, but storage capabilities cannot be exposed",
953                            storage
954                        ),
955                        vec![storages.origin.clone()],
956                    ));
957                }
958            }
959        }
960
961        for from_ref in offer.from.value.iter() {
962            if let OfferFromRef::Dictionary(_) = &from_ref {
963                if let Some(storage_span) = offer.storage.as_ref() {
964                    return Err(Error::validate_context(
965                        "Dictionaries do not support \"storage\" capabilities",
966                        Some(storage_span.origin.clone()),
967                    ));
968                }
969                if let Some(stream_span) = offer.event_stream.as_ref() {
970                    return Err(Error::validate_context(
971                        "Dictionaries do not support \"event_stream\" capabilities",
972                        Some(stream_span.origin.clone()),
973                    ));
974                }
975            }
976        }
977
978        if !offer_can_have_dependency(offer) {
979            if let Some(dep_span) = offer.dependency.as_ref() {
980                return Err(Error::validate_context(
981                    "Dependency can only be provided for protocol, directory, and service capabilities",
982                    Some(dep_span.origin.clone()),
983                ));
984            }
985        }
986
987        let target_cap_ids_with_origin = CapabilityId::from_context_offer(offer_wrapper)?;
988
989        let to_wrapper = &offer.to;
990
991        let to_field_origin = &to_wrapper.origin;
992        let to_targets = &to_wrapper.value;
993
994        for target_ref in to_targets.iter() {
995            let to_target = match target_ref {
996                OfferToRef::All => continue,
997                OfferToRef::Named(to_target) => {
998                    // Verify that only a legal set of offers-to-all are made, including that any
999                    // offer to all duplicated as an offer to a specific component are exactly the same
1000                    for offer_to_all in protocols_offered_to_all {
1001                        offer_to_all_would_duplicate_context(
1002                            offer_to_all,
1003                            offer_wrapper,
1004                            &to_target,
1005                        )?;
1006                    }
1007
1008                    let target_availability_is_unknown = offer
1009                        .target_availability
1010                        .as_ref()
1011                        .map_or(false, |a| a.value == TargetAvailability::Unknown);
1012
1013                    // Check that any referenced child actually exists.
1014                    if self.all_children.contains::<BorrowedName>(to_target.as_ref())
1015                        || self.all_collections.contains::<BorrowedName>(to_target.as_ref())
1016                        || target_availability_is_unknown
1017                    {
1018                        // Allowed.
1019                    } else {
1020                        if let OneOrMany::One(from) = &offer.from.value {
1021                            return Err(Error::validate_context(
1022                                format!(
1023                                    "\"{target_ref}\" is an \"offer\" target from \"{from}\" but \"{target_ref}\" does \
1024                            not appear in \"children\" or \"collections\"",
1025                                ),
1026                                Some(to_field_origin.clone()),
1027                            ));
1028                        } else {
1029                            return Err(Error::validate_context(
1030                                format!(
1031                                    "\"{target_ref}\" is an \"offer\" target but \"{target_ref}\" does not appear in \
1032                            \"children\" or \"collections\"",
1033                                ),
1034                                Some(to_field_origin.clone()),
1035                            ));
1036                        }
1037                    }
1038
1039                    // Ensure we are not offering a capability back to its source.
1040                    if let Some(storage) = offer.storage.as_ref() {
1041                        for storage in &storage.value {
1042                            // Storage can only have a single `from` clause and this has been
1043                            // verified.
1044                            if let OneOrMany::One(OfferFromRef::Self_) = &offer.from.value {
1045                                if let Some(CapabilityFromRef::Named(source)) =
1046                                    self.all_storages.get::<BorrowedName>(storage.as_ref())
1047                                {
1048                                    if *to_target == *source {
1049                                        return Err(Error::validate_context(
1050                                            format!(
1051                                                "Storage offer target \"{}\" is same as source",
1052                                                target_ref
1053                                            ),
1054                                            Some(to_field_origin.clone()),
1055                                        ));
1056                                    }
1057                                }
1058                            }
1059                        }
1060                    } else {
1061                        for reference in offer.from.value.iter() {
1062                            // Weak offers from a child to itself are acceptable.
1063                            if offer_dependency(offer) == DependencyType::Weak {
1064                                continue;
1065                            }
1066                            match reference {
1067                                OfferFromRef::Named(name) if name == to_target => {
1068                                    return Err(Error::validate_context(
1069                                        format!(
1070                                            "Offer target \"{}\" is same as source",
1071                                            target_ref
1072                                        ),
1073                                        Some(to_field_origin.clone()),
1074                                    ));
1075                                }
1076                                _ => {}
1077                            }
1078                        }
1079                    }
1080                    to_target
1081                }
1082                OfferToRef::OwnDictionary(to_target) => {
1083                    let r2 = CapabilityId::from_context_offer(offer_wrapper)?;
1084                    for (id, cap_origin) in r2 {
1085                        match &id {
1086                            CapabilityId::Protocol(_)
1087                            | CapabilityId::Dictionary(_)
1088                            | CapabilityId::Directory(_)
1089                            | CapabilityId::Runner(_)
1090                            | CapabilityId::Resolver(_)
1091                            | CapabilityId::Service(_)
1092                            | CapabilityId::Configuration(_) => {}
1093                            CapabilityId::Storage(_) | CapabilityId::EventStream(_) => {
1094                                let type_name = id.type_str();
1095                                return Err(Error::validate_context(
1096                                    format!(
1097                                        "\"offer\" to dictionary \"{target_ref}\" for \"{type_name}\" but \
1098                                    dictionaries do not support this type yet."
1099                                    ),
1100                                    Some(cap_origin),
1101                                ));
1102                            }
1103                            CapabilityId::UsedService(_)
1104                            | CapabilityId::UsedProtocol(_)
1105                            | CapabilityId::UsedProtocolNumberedHandle(_)
1106                            | CapabilityId::UsedDirectory(_)
1107                            | CapabilityId::UsedStorage(_)
1108                            | CapabilityId::UsedEventStream(_)
1109                            | CapabilityId::UsedRunner(_)
1110                            | CapabilityId::UsedConfiguration(_)
1111                            | CapabilityId::UsedDictionary(_) => {
1112                                unreachable!("this is not a use")
1113                            }
1114                        }
1115                    }
1116                    // Check that any referenced child actually exists.
1117                    let target_availability_is_unknown = offer
1118                        .target_availability
1119                        .as_ref()
1120                        .map_or(false, |a| a.value == TargetAvailability::Unknown);
1121
1122                    // Check that any referenced dictionary actually exists.
1123                    if let Some(d) = self.all_dictionaries.get::<BorrowedName>(to_target.as_ref()) {
1124                        // If it exists, verify it's not dynamic
1125                        if d.path.is_some() {
1126                            return Err(Error::validate_context(
1127                                format!(
1128                                    "\"offer\" has dictionary target \"{target_ref}\" but \"{to_target}\" \
1129                                sets \"path\". Therefore, it is a dynamic dictionary that \
1130                                does not allow offers into it."
1131                                ),
1132                                Some(to_field_origin.clone()),
1133                            ));
1134                        }
1135                    } else if !target_availability_is_unknown {
1136                        // If it doesn't exist, ONLY error if availability is NOT unknown
1137                        return Err(Error::validate_context(
1138                            format!(
1139                                "\"offer\" has dictionary target \"{target_ref}\" but \"{to_target}\" \
1140                                is not a dictionary capability defined by this component"
1141                            ),
1142                            Some(to_field_origin.clone()),
1143                        ));
1144                    }
1145                    to_target
1146                }
1147            };
1148
1149            // Ensure that a target is not offered more than once.
1150            let ids_for_entity = used_ids.entry(to_target.clone()).or_insert(HashMap::new());
1151            for (target_cap_id, target_origin) in &target_cap_ids_with_origin {
1152                if let Some(conflict_origin) =
1153                    ids_for_entity.insert(target_cap_id.to_string(), target_origin.clone())
1154                {
1155                    if let CapabilityId::Service(_) = target_cap_id {
1156                        // Services may have duplicates (aggregation).
1157                    } else {
1158                        return Err(Error::validate_contexts(
1159                            format!(
1160                                "\"{}\" is a duplicate \"offer\" target capability for \"{}\"",
1161                                target_cap_id, target_ref
1162                            ),
1163                            vec![target_origin.clone(), conflict_origin],
1164                        ));
1165                    }
1166                }
1167            }
1168        }
1169
1170        self.validate_from_clause(
1171            "offer",
1172            offer,
1173            &offer.source_availability.as_ref().map(|s| s.value.clone()),
1174            &offer.availability.as_ref().map(|s| s.value.clone()),
1175            offer.from.origin.clone(),
1176        )?;
1177
1178        Ok(())
1179    }
1180
1181    fn validate_collection(
1182        &mut self,
1183        collection: &'a ContextSpanned<ContextCollection>,
1184    ) -> Result<(), Error> {
1185        if collection.value.allow_long_names.is_some() {
1186            self.features.check(Feature::AllowLongNames)?;
1187        }
1188        Ok(())
1189    }
1190
1191    fn validate_environment(
1192        &mut self,
1193        environment_wrapper: &'a ContextSpanned<ContextEnvironment>,
1194    ) -> Result<(), Error> {
1195        let environment = &environment_wrapper.value;
1196
1197        if let Some(extends_span) = &environment.extends {
1198            if extends_span.value == EnvironmentExtends::None {
1199                if environment.stop_timeout_ms.is_none() {
1200                    return Err(Error::validate_context(
1201                        "'__stop_timeout_ms' must be provided if the environment extends 'none'",
1202                        Some(extends_span.origin.clone()),
1203                    ));
1204                }
1205            }
1206        }
1207
1208        if let Some(runners) = &environment.runners {
1209            let mut used_names = HashMap::new();
1210            for registration_span in runners {
1211                let reg = &registration_span.value;
1212
1213                let (target_name, name_origin) = if let Some(as_span) = &reg.r#as {
1214                    (&as_span.value, &as_span.origin)
1215                } else {
1216                    (&reg.runner.value, &reg.runner.origin)
1217                };
1218
1219                if let Some((prev_runner, prev_origin)) =
1220                    used_names.insert(target_name, (&reg.runner.value, name_origin))
1221                {
1222                    return Err(Error::validate_contexts(
1223                        format!(
1224                            "Duplicate runners registered under name \"{}\": \"{}\" and \"{}\".",
1225                            target_name, &reg.runner.value, prev_runner
1226                        ),
1227                        vec![prev_origin.clone(), name_origin.clone()],
1228                    ));
1229                }
1230
1231                // Ensure runner exists if source is 'self'
1232                let runner_ref: Name = reg.runner.value.clone();
1233                if reg.from.value == RegistrationRef::Self_
1234                    && !self.all_runners.contains(&runner_ref)
1235                {
1236                    return Err(Error::validate_context(
1237                        format!(
1238                            "Runner \"{}\" is not defined in the root \"runners\" section",
1239                            &reg.runner.value
1240                        ),
1241                        Some(reg.runner.origin.clone()),
1242                    ));
1243                }
1244
1245                self.validate_component_child_ref(
1246                    &format!("\"{}\" runner source", &reg.runner.value),
1247                    &AnyRef::from(&reg.from.value),
1248                    Some(&reg.from.origin),
1249                )?;
1250            }
1251        }
1252
1253        if let Some(resolvers) = &environment.resolvers {
1254            let mut used_schemes = HashMap::new();
1255            for registration_span in resolvers {
1256                let reg = &registration_span.value;
1257
1258                if let Some((prev_resolver, prev_origin)) = used_schemes
1259                    .insert(&reg.scheme.value, (&reg.resolver.value, &reg.scheme.origin))
1260                {
1261                    return Err(Error::validate_contexts(
1262                        format!(
1263                            "scheme \"{}\" for resolver \"{}\" is already registered to \"{}\".",
1264                            &reg.scheme.value, &reg.resolver.value, prev_resolver
1265                        ),
1266                        vec![prev_origin.clone(), reg.scheme.origin.clone()],
1267                    ));
1268                }
1269
1270                self.validate_component_child_ref(
1271                    &format!("\"{}\" resolver source", &reg.resolver.value),
1272                    &AnyRef::from(&reg.from.value),
1273                    Some(&reg.from.origin),
1274                )?;
1275            }
1276        }
1277
1278        if let Some(debug_capabilities) = &environment.debug {
1279            for debug_span in debug_capabilities {
1280                let debug = &debug_span.value;
1281                self.protocol_from_self_checker(debug).validate("registered as debug")?;
1282                self.validate_from_clause("debug", debug, &None, &None, debug.from.origin.clone())?;
1283            }
1284        }
1285
1286        Ok(())
1287    }
1288
1289    fn get_test_facet(&self) -> Option<&ContextSpanned<serde_json::Value>> {
1290        match &self.document.facets {
1291            Some(m) => m.get(TEST_FACET_KEY),
1292            None => None,
1293        }
1294    }
1295
1296    fn validate_facets(&self) -> Result<(), Error> {
1297        let test_facet_spanned = self.get_test_facet();
1298        let enable_allow_non_hermetic_packages =
1299            self.features.has(&Feature::EnableAllowNonHermeticPackagesFeature);
1300
1301        if let Some(spanned) = test_facet_spanned {
1302            let test_facet_origin = &spanned.origin;
1303            let test_facet_map = match &spanned.value {
1304                serde_json::Value::Object(m) => m,
1305                _ => {
1306                    return Err(Error::validate_context(
1307                        format!("'{}' is not an object", TEST_FACET_KEY),
1308                        Some(test_facet_origin.clone()),
1309                    ));
1310                }
1311            };
1312
1313            let restrict_test_type = self.features.has(&Feature::RestrictTestTypeInFacet);
1314
1315            if restrict_test_type {
1316                if test_facet_map.contains_key(TEST_TYPE_FACET_KEY) {
1317                    return Err(Error::validate_context(
1318                        format!(
1319                            "'{}' is not allowed in facets. Refer to: \
1320                            https://fuchsia.dev/fuchsia-src/development/testing/components/test_runner_framework#non-hermetic_tests",
1321                            TEST_TYPE_FACET_KEY
1322                        ),
1323                        Some(test_facet_origin.clone()),
1324                    ));
1325                }
1326            }
1327        }
1328
1329        if enable_allow_non_hermetic_packages {
1330            let allow_non_hermetic_packages = self.features.has(&Feature::AllowNonHermeticPackages);
1331
1332            let has_deprecated_facet = test_facet_spanned
1333                .and_then(|s| s.value.as_object())
1334                .map_or(false, |m| m.contains_key(TEST_DEPRECATED_ALLOWED_PACKAGES_FACET_KEY));
1335
1336            if allow_non_hermetic_packages && !has_deprecated_facet {
1337                return Err(Error::validate(format!(
1338                    "Remove restricted_feature '{}' as manifest does not contain facet '{}'",
1339                    Feature::AllowNonHermeticPackages,
1340                    TEST_DEPRECATED_ALLOWED_PACKAGES_FACET_KEY
1341                )));
1342            }
1343
1344            if has_deprecated_facet && !allow_non_hermetic_packages {
1345                let origin = test_facet_spanned.map(|s| s.origin.clone());
1346
1347                return Err(Error::validate_context(
1348                    format!(
1349                        "restricted_feature '{}' should be present with facet '{}'",
1350                        Feature::AllowNonHermeticPackages,
1351                        TEST_DEPRECATED_ALLOWED_PACKAGES_FACET_KEY
1352                    ),
1353                    origin,
1354                ));
1355            }
1356        }
1357
1358        Ok(())
1359    }
1360
1361    fn validate_config(&self) -> Result<(), Error> {
1362        let fields = self.document.config.as_ref();
1363
1364        let optional_use_keys: BTreeMap<ConfigKey, ContextSpanned<ConfigValueType>> = self
1365            .document
1366            .r#use
1367            .iter()
1368            .flatten()
1369            .filter_map(|u_spanned| {
1370                let u = &u_spanned.value;
1371                if u.config.is_none() {
1372                    return None;
1373                }
1374
1375                if u.availability.as_ref().map(|s| s.value) != Some(Availability::Optional) {
1376                    return None;
1377                }
1378
1379                if u.config_default.is_some() {
1380                    return None;
1381                }
1382
1383                let key =
1384                    ConfigKey(u.key.as_ref().expect("key should be set").value.clone().into());
1385                let value_type =
1386                    use_config_to_value_type_context(u).expect("config type should be valid");
1387
1388                Some((key, ContextSpanned { value: value_type, origin: u_spanned.origin.clone() }))
1389            })
1390            .collect();
1391
1392        let Some(fields_map) = fields else {
1393            if !optional_use_keys.is_empty() {
1394                return Err(Error::validate(
1395                    "Optionally using a config capability without a default requires a matching 'config' section.",
1396                ));
1397            }
1398            return Ok(());
1399        };
1400
1401        if fields_map.is_empty() {
1402            return Err(Error::validate("'config' section is empty"));
1403        }
1404
1405        for (key, use_spanned) in optional_use_keys {
1406            match fields_map.get(&key) {
1407                None => {
1408                    return Err(Error::validate_context(
1409                        format!("'config' section must contain key for optional use '{}'", key),
1410                        Some(use_spanned.origin),
1411                    ));
1412                }
1413                Some(config_spanned) => {
1414                    if config_spanned.value != use_spanned.value {
1415                        return Err(Error::validate_contexts(
1416                            format!("Use and config block differ on type for key '{}'", key),
1417                            vec![use_spanned.origin, config_spanned.origin.clone()],
1418                        ));
1419                    }
1420                }
1421            }
1422        }
1423
1424        Ok(())
1425    }
1426
1427    /// Validates that directory rights for all route types are valid, i.e that it does not
1428    /// contain duplicate rights.
1429    fn validate_directory_rights(
1430        &self,
1431        rights_clause: &Rights,
1432        origin: Option<&Arc<PathBuf>>,
1433    ) -> Result<(), Error> {
1434        let mut rights = HashSet::new();
1435        for right_token in rights_clause.0.iter() {
1436            for right in right_token.expand() {
1437                if !rights.insert(right) {
1438                    return Err(Error::validate_context(
1439                        format!("\"{}\" is duplicated in the rights clause.", right_token),
1440                        origin.cloned(),
1441                    ));
1442                }
1443            }
1444        }
1445        Ok(())
1446    }
1447
1448    fn from_self(&self, from_one_or_many: &OneOrMany<OfferFromRef>) -> bool {
1449        for from_ref in from_one_or_many.iter() {
1450            match from_ref {
1451                OfferFromRef::Self_ => return true,
1452                _ => {}
1453            }
1454        }
1455        false
1456    }
1457
1458    /// Validates that the from clause:
1459    ///
1460    /// - is applicable to the capability type,
1461    /// - does not contain duplicates,
1462    /// - references names that exist.
1463    /// - has availability "optional" if the source is "void"
1464    ///
1465    /// `verb` is used in any error messages and is expected to be "offer", "expose", etc.
1466    fn validate_from_clause<T>(
1467        &self,
1468        verb: &str,
1469        cap: &T,
1470        source_availability: &Option<SourceAvailability>,
1471        availability: &Option<Availability>,
1472        origin: Arc<PathBuf>,
1473    ) -> Result<(), Error>
1474    where
1475        T: ContextCapabilityClause + FromClauseContext,
1476    {
1477        let from = cap.from_();
1478        if cap.service().is_none() && from.value.is_many() {
1479            return Err(Error::validate_context(
1480                format!(
1481                    "\"{}\" capabilities cannot have multiple \"from\" clauses",
1482                    cap.capability_type(None).unwrap()
1483                ),
1484                Some(origin),
1485            ));
1486        }
1487
1488        let from_val = &from.value;
1489
1490        if from_val.is_many() {
1491            ensure_no_duplicate_values(from_val.iter())?;
1492        }
1493
1494        let reference_description = format!("\"{}\" source", verb);
1495        for from_clause in from_val {
1496            // If this is a protocol, it could reference either a child or a storage capability
1497            // (for the storage admin protocol).
1498            let ref_validity_res = if cap.protocol().is_some() {
1499                self.validate_component_child_or_capability_ref(
1500                    &reference_description,
1501                    &from_clause,
1502                    Some(&origin),
1503                )
1504            } else if cap.service().is_some() {
1505                // Services can also be sourced from collections.
1506                self.validate_component_child_or_collection_ref(
1507                    &reference_description,
1508                    &from_clause,
1509                    Some(&origin),
1510                )
1511            } else {
1512                self.validate_component_child_ref(
1513                    &reference_description,
1514                    &from_clause,
1515                    Some(&origin),
1516                )
1517            };
1518
1519            match ref_validity_res {
1520                Ok(()) if *from_clause == AnyRef::Void => {
1521                    // The source is valid and void
1522                    if availability != &Some(Availability::Optional) {
1523                        return Err(Error::validate_context(
1524                            format!(
1525                                "capabilities with a source of \"void\" must have an availability of \"optional\", capabilities: \"{}\", from: \"{}\"",
1526                                cap.names()
1527                                    .iter()
1528                                    .map(|n| n.value.as_str())
1529                                    .collect::<Vec<_>>()
1530                                    .join(", "),
1531                                cap.from_().value,
1532                            ),
1533                            Some(origin),
1534                        ));
1535                    }
1536                }
1537                Ok(()) => {
1538                    // The source is valid and not void.
1539                }
1540                Err(_) if source_availability == &Some(SourceAvailability::Unknown) => {
1541                    // The source is invalid, and will be rewritten to void
1542                    if availability != &Some(Availability::Optional) && availability != &None {
1543                        return Err(Error::validate_context(
1544                            format!(
1545                                "capabilities with an intentionally missing source must have an availability that is either unset or \"optional\", capabilities: \"{}\", from: \"{}\"",
1546                                cap.names()
1547                                    .iter()
1548                                    .map(|n| n.value.as_str())
1549                                    .collect::<Vec<_>>()
1550                                    .join(", "),
1551                                cap.from_().value,
1552                            ),
1553                            Some(origin),
1554                        ));
1555                    }
1556                }
1557                Err(e) => {
1558                    // The source is invalid, but we're expecting it to be valid.
1559                    return Err(e);
1560                }
1561            }
1562        }
1563        Ok(())
1564    }
1565
1566    /// Validates that the given component exists.
1567    ///
1568    /// - `reference_description` is a human-readable description of the reference used in error
1569    ///   message, such as `"offer" source`.
1570    /// - `component_ref` is a reference to a component. If the reference is a named child, we
1571    ///   ensure that the child component exists.
1572    fn validate_component_child_ref(
1573        &self,
1574        reference_description: &str,
1575        component_ref: &AnyRef<'_>,
1576        origin: Option<&Arc<PathBuf>>,
1577    ) -> Result<(), Error> {
1578        match component_ref {
1579            AnyRef::Named(name) => {
1580                // Ensure we have a child defined by that name.
1581                if !self.all_children.contains(*name) {
1582                    return Err(Error::validate_context(
1583                        format!(
1584                            "{} \"{}\" does not appear in \"children\"",
1585                            reference_description, component_ref
1586                        ),
1587                        origin.cloned(),
1588                    ));
1589                }
1590                Ok(())
1591            }
1592            // We don't attempt to validate other reference types.
1593            _ => Ok(()),
1594        }
1595    }
1596
1597    /// Validates that the given component/collection exists.
1598    ///
1599    /// - `reference_description` is a human-readable description of the reference used in error
1600    ///   message, such as `"offer" source`.
1601    /// - `component_ref` is a reference to a component/collection. If the reference is a named
1602    ///   child or collection, we ensure that the child component/collection exists.
1603    fn validate_component_child_or_collection_ref(
1604        &self,
1605        reference_description: &str,
1606        component_ref: &AnyRef<'_>,
1607        origin: Option<&Arc<PathBuf>>,
1608    ) -> Result<(), Error> {
1609        match component_ref {
1610            AnyRef::Named(name) => {
1611                // Ensure we have a child or collection defined by that name.
1612                if !self.all_children.contains(*name) && !self.all_collections.contains(*name) {
1613                    return Err(Error::validate_context(
1614                        format!(
1615                            "{} \"{}\" does not appear in \"children\" or \"collections\"",
1616                            reference_description, component_ref
1617                        ),
1618                        origin.cloned(),
1619                    ));
1620                }
1621                Ok(())
1622            }
1623            // We don't attempt to validate other reference types.
1624            _ => Ok(()),
1625        }
1626    }
1627
1628    /// Validates that the given capability exists.
1629    ///
1630    /// - `reference_description` is a human-readable description of the reference used in error
1631    ///   message, such as `"offer" source`.
1632    /// - `capability_ref` is a reference to a capability. If the reference is a named capability,
1633    ///   we ensure that the capability exists.
1634    fn validate_component_capability_ref(
1635        &self,
1636        reference_description: &str,
1637        capability_ref: &AnyRef<'_>,
1638        origin: Option<&Arc<PathBuf>>,
1639    ) -> Result<(), Error> {
1640        match capability_ref {
1641            AnyRef::Named(name) => {
1642                if !self.all_capability_names.contains(*name) {
1643                    return Err(Error::validate_context(
1644                        format!(
1645                            "{} \"{}\" does not appear in \"capabilities\"",
1646                            reference_description, capability_ref
1647                        ),
1648                        origin.cloned(),
1649                    ));
1650                }
1651                Ok(())
1652            }
1653            _ => Ok(()),
1654        }
1655    }
1656
1657    /// Validates that the given child component, collection, or capability exists.
1658    ///
1659    /// - `reference_description` is a human-readable description of the reference used in error
1660    ///   message, such as `"offer" source`.
1661    /// - `ref_` is a reference to a child component or capability. If the reference contains a
1662    ///   name, we ensure that a child component or a capability with the name exists.
1663    fn validate_component_child_or_capability_ref(
1664        &self,
1665        reference_description: &str,
1666        ref_: &AnyRef<'_>,
1667        origin: Option<&Arc<PathBuf>>,
1668    ) -> Result<(), Error> {
1669        if self.validate_component_child_ref(reference_description, ref_, origin).is_err()
1670            && self.validate_component_capability_ref(reference_description, ref_, origin).is_err()
1671        {
1672            return Err(Error::validate_context(
1673                format!(
1674                    "{} \"{}\" does not appear in \"children\" or \"capabilities\"",
1675                    reference_description, ref_
1676                ),
1677                origin.cloned(),
1678            ));
1679        }
1680        Ok(())
1681    }
1682
1683    fn protocol_from_self_checker<'b>(
1684        &'b self,
1685        input: &'b (impl ContextCapabilityClause + FromClauseContext),
1686    ) -> RouteFromSelfCheckerV2<'b> {
1687        RouteFromSelfCheckerV2 {
1688            capability_name: input.protocol().map(|spanned| {
1689                spanned.map(|one_or_many| match one_or_many {
1690                    OneOrMany::One(name) => OneOrMany::One(AnyRef::from(name)),
1691                    OneOrMany::Many(names) => {
1692                        OneOrMany::Many(names.iter().cloned().map(AnyRef::from).collect())
1693                    }
1694                })
1695            }),
1696            from: input.from_(),
1697            container: &self.all_protocols,
1698            all_dictionaries: &self.all_dictionaries,
1699            typename: "protocol",
1700        }
1701    }
1702
1703    fn resolver_from_self_checker<'b>(
1704        &'b self,
1705        input: &'b (impl ContextCapabilityClause + FromClauseContext),
1706    ) -> RouteFromSelfCheckerV2<'b> {
1707        RouteFromSelfCheckerV2 {
1708            capability_name: input.resolver().map(|spanned| {
1709                spanned.map(|one_or_many| match one_or_many {
1710                    OneOrMany::One(name) => OneOrMany::One(AnyRef::from(name)),
1711                    OneOrMany::Many(names) => {
1712                        OneOrMany::Many(names.iter().cloned().map(AnyRef::from).collect())
1713                    }
1714                })
1715            }),
1716            from: input.from_(),
1717            container: &self.all_resolvers,
1718            all_dictionaries: &self.all_dictionaries,
1719            typename: "resolver",
1720        }
1721    }
1722
1723    fn runner_from_self_checker<'b>(
1724        &'b self,
1725        input: &'b (impl ContextCapabilityClause + FromClauseContext),
1726    ) -> RouteFromSelfCheckerV2<'b> {
1727        RouteFromSelfCheckerV2 {
1728            capability_name: input.runner().map(|spanned| {
1729                spanned.map(|one_or_many| match one_or_many {
1730                    OneOrMany::One(name) => OneOrMany::One(AnyRef::from(name)),
1731                    OneOrMany::Many(names) => {
1732                        OneOrMany::Many(names.iter().cloned().map(AnyRef::from).collect())
1733                    }
1734                })
1735            }),
1736            from: input.from_(),
1737            container: &self.all_runners,
1738            all_dictionaries: &self.all_dictionaries,
1739            typename: "runner",
1740        }
1741    }
1742
1743    fn service_from_self_checker<'b>(
1744        &'b self,
1745        input: &'b (impl ContextCapabilityClause + FromClauseContext),
1746    ) -> RouteFromSelfCheckerV2<'b> {
1747        RouteFromSelfCheckerV2 {
1748            capability_name: input.service().map(|spanned| {
1749                spanned.map(|one_or_many| match one_or_many {
1750                    OneOrMany::One(name) => OneOrMany::One(AnyRef::from(name)),
1751                    OneOrMany::Many(names) => {
1752                        OneOrMany::Many(names.iter().cloned().map(AnyRef::from).collect())
1753                    }
1754                })
1755            }),
1756            from: input.from_(),
1757            container: &self.all_services,
1758            all_dictionaries: &self.all_dictionaries,
1759            typename: "service",
1760        }
1761    }
1762
1763    fn config_from_self_checker<'b>(
1764        &'b self,
1765        input: &'b (impl ContextCapabilityClause + FromClauseContext),
1766    ) -> RouteFromSelfCheckerV2<'b> {
1767        RouteFromSelfCheckerV2 {
1768            capability_name: input.config().map(|spanned| {
1769                spanned.map(|one_or_many| match one_or_many {
1770                    OneOrMany::One(name) => OneOrMany::One(AnyRef::from(name)),
1771                    OneOrMany::Many(names) => {
1772                        OneOrMany::Many(names.iter().cloned().map(AnyRef::from).collect())
1773                    }
1774                })
1775            }),
1776            from: input.from_(),
1777            container: &self.all_configs,
1778            all_dictionaries: &self.all_dictionaries,
1779            typename: "config",
1780        }
1781    }
1782
1783    fn dictionary_from_self_checker<'b>(
1784        &'b self,
1785        input: &'b (impl ContextCapabilityClause + FromClauseContext),
1786    ) -> RouteFromSelfCheckerV2<'b> {
1787        RouteFromSelfCheckerV2 {
1788            capability_name: input.dictionary().map(|spanned| {
1789                spanned.map(|one_or_many| match one_or_many {
1790                    OneOrMany::One(name) => OneOrMany::One(AnyRef::from(name)),
1791                    OneOrMany::Many(names) => {
1792                        OneOrMany::Many(names.iter().cloned().map(AnyRef::from).collect())
1793                    }
1794                })
1795            }),
1796            from: input.from_(),
1797            container: &self.all_dictionaries,
1798            all_dictionaries: &self.all_dictionaries,
1799            typename: "dictionary",
1800        }
1801    }
1802
1803    fn directory_from_self_checker<'b>(
1804        &'b self,
1805        input: &'b (impl ContextCapabilityClause + FromClauseContext),
1806    ) -> RouteFromSelfCheckerV2<'b> {
1807        RouteFromSelfCheckerV2 {
1808            capability_name: input.directory().map(|spanned| {
1809                spanned.map(|one_or_many| match one_or_many {
1810                    OneOrMany::One(name) => OneOrMany::One(AnyRef::from(name)),
1811                    OneOrMany::Many(names) => {
1812                        OneOrMany::Many(names.iter().cloned().map(AnyRef::from).collect())
1813                    }
1814                })
1815            }),
1816            from: input.from_(),
1817            container: &self.all_directories,
1818            all_dictionaries: &self.all_dictionaries,
1819            typename: "directory",
1820        }
1821    }
1822
1823    fn storage_from_self_checker<'b>(
1824        &'b self,
1825        input: &'b (impl ContextCapabilityClause + FromClauseContext),
1826    ) -> RouteFromSelfCheckerV2<'b> {
1827        RouteFromSelfCheckerV2 {
1828            capability_name: input.storage().map(|spanned| {
1829                spanned.map(|one_or_many| match one_or_many {
1830                    OneOrMany::One(name) => OneOrMany::One(AnyRef::from(name)),
1831                    OneOrMany::Many(names) => {
1832                        OneOrMany::Many(names.iter().cloned().map(AnyRef::from).collect())
1833                    }
1834                })
1835            }),
1836            from: input.from_(),
1837            container: &self.all_storages,
1838            all_dictionaries: &self.all_dictionaries,
1839            typename: "storage",
1840        }
1841    }
1842}
1843
1844struct ValidationContext<'a> {
1845    document: &'a Document,
1846    features: &'a FeatureSet,
1847    capability_requirements: &'a CapabilityRequirements<'a>,
1848    all_children: HashMap<&'a BorrowedName, &'a Child>,
1849    all_collections: HashSet<&'a BorrowedName>,
1850    all_storages: HashMap<&'a BorrowedName, &'a CapabilityFromRef>,
1851    all_services: HashSet<&'a BorrowedName>,
1852    all_protocols: HashSet<&'a BorrowedName>,
1853    all_directories: HashSet<&'a BorrowedName>,
1854    all_runners: HashSet<&'a BorrowedName>,
1855    all_resolvers: HashSet<&'a BorrowedName>,
1856    all_dictionaries: HashMap<&'a BorrowedName, &'a Capability>,
1857    all_configs: HashSet<&'a BorrowedName>,
1858    all_environment_names: HashSet<&'a BorrowedName>,
1859    all_capability_names: HashSet<&'a BorrowedName>,
1860}
1861
1862// Facet key for fuchsia.test
1863const TEST_FACET_KEY: &'static str = "fuchsia.test";
1864
1865// Facet key for deprecated-allowed-packages.
1866const TEST_DEPRECATED_ALLOWED_PACKAGES_FACET_KEY: &'static str = "deprecated-allowed-packages";
1867
1868// Facet key for type.
1869const TEST_TYPE_FACET_KEY: &'static str = "type";
1870
1871impl<'a> ValidationContext<'a> {
1872    fn new(
1873        document: &'a Document,
1874        features: &'a FeatureSet,
1875        capability_requirements: &'a CapabilityRequirements<'a>,
1876    ) -> Self {
1877        ValidationContext {
1878            document,
1879            features,
1880            capability_requirements,
1881            all_children: HashMap::new(),
1882            all_collections: HashSet::new(),
1883            all_storages: HashMap::new(),
1884            all_services: HashSet::new(),
1885            all_protocols: HashSet::new(),
1886            all_directories: HashSet::new(),
1887            all_runners: HashSet::new(),
1888            all_resolvers: HashSet::new(),
1889            all_dictionaries: HashMap::new(),
1890            all_configs: HashSet::new(),
1891            all_environment_names: HashSet::new(),
1892            all_capability_names: HashSet::new(),
1893        }
1894    }
1895
1896    fn validate(&mut self) -> Result<(), Error> {
1897        // Ensure child components, collections, and storage don't use the
1898        // same name.
1899        //
1900        // We currently have the ability to distinguish between storage and
1901        // children/collections based on context, but still enforce name
1902        // uniqueness to give us flexibility in future.
1903        let all_children_names =
1904            self.document.all_children_names().into_iter().zip(iter::repeat("children"));
1905        let all_collection_names =
1906            self.document.all_collection_names().into_iter().zip(iter::repeat("collections"));
1907        let all_storage_names =
1908            self.document.all_storage_names().into_iter().zip(iter::repeat("storage"));
1909        let all_runner_names =
1910            self.document.all_runner_names().into_iter().zip(iter::repeat("runners"));
1911        let all_resolver_names =
1912            self.document.all_resolver_names().into_iter().zip(iter::repeat("resolvers"));
1913        let all_environment_names =
1914            self.document.all_environment_names().into_iter().zip(iter::repeat("environments"));
1915        let all_dictionary_names =
1916            self.document.all_dictionary_names().into_iter().zip(iter::repeat("dictionaries"));
1917        ensure_no_duplicate_names(
1918            all_children_names
1919                .chain(all_collection_names)
1920                .chain(all_storage_names)
1921                .chain(all_runner_names)
1922                .chain(all_resolver_names)
1923                .chain(all_environment_names)
1924                .chain(all_dictionary_names),
1925        )?;
1926
1927        // Populate the sets of children and collections.
1928        if let Some(children) = &self.document.children {
1929            self.all_children = children.iter().map(|c| (c.name.as_ref(), c)).collect();
1930        }
1931        self.all_collections = self.document.all_collection_names().into_iter().collect();
1932        self.all_storages = self.document.all_storage_with_sources();
1933        self.all_services = self.document.all_service_names().into_iter().collect();
1934        self.all_protocols = self.document.all_protocol_names().into_iter().collect();
1935        self.all_directories = self.document.all_directory_names().into_iter().collect();
1936        self.all_runners = self.document.all_runner_names().into_iter().collect();
1937        self.all_resolvers = self.document.all_resolver_names().into_iter().collect();
1938        self.all_dictionaries = self.document.all_dictionaries().into_iter().collect();
1939        self.all_configs = self.document.all_config_names().into_iter().collect();
1940        self.all_environment_names = self.document.all_environment_names().into_iter().collect();
1941        self.all_capability_names = self.document.all_capability_names();
1942
1943        // Validate "children".
1944        if let Some(children) = &self.document.children {
1945            for child in children {
1946                self.validate_child(&child)?;
1947            }
1948        }
1949
1950        // Validate "collections".
1951        if let Some(collections) = &self.document.collections {
1952            for collection in collections {
1953                self.validate_collection(&collection)?;
1954            }
1955        }
1956
1957        // Validate "capabilities".
1958        if let Some(capabilities) = self.document.capabilities.as_ref() {
1959            let mut used_ids = HashMap::new();
1960            for capability in capabilities {
1961                self.validate_capability(capability, &mut used_ids)?;
1962            }
1963        }
1964
1965        // Validate "use".
1966        let mut uses_runner = false;
1967        if let Some(uses) = self.document.r#use.as_ref() {
1968            let mut used_ids = HashMap::new();
1969            for use_ in uses.iter() {
1970                self.validate_use(&use_, &mut used_ids)?;
1971                if use_.runner.is_some() {
1972                    uses_runner = true;
1973                }
1974            }
1975        }
1976
1977        // Validate "expose".
1978        if let Some(exposes) = self.document.expose.as_ref() {
1979            let mut used_ids = HashMap::new();
1980            let mut exposed_to_framework_ids = HashMap::new();
1981            for expose in exposes.iter() {
1982                self.validate_expose(&expose, &mut used_ids, &mut exposed_to_framework_ids)?;
1983            }
1984        }
1985
1986        // Validate "offer".
1987        if let Some(offers) = self.document.offer.as_ref() {
1988            let mut used_ids = HashMap::new();
1989
1990            let mut duplicate_check: HashSet<CapabilityId<'a>> = HashSet::new();
1991            let mut problem_protocols = Vec::new();
1992            let mut problem_dictionaries = Vec::new();
1993
1994            offers
1995                .iter()
1996                .filter(|o| matches!(o.to, OneOrMany::One(OfferToRef::All)))
1997                .try_for_each(|offer| -> Result<(), Error> {
1998                    if offer.protocol.is_some() {
1999                        for cap_id in CapabilityId::from_offer_expose(offer)? {
2000                            if !duplicate_check.insert(cap_id.clone()) {
2001                                problem_protocols.push(cap_id);
2002                            }
2003                        }
2004                    }
2005
2006                    if offer.dictionary.is_some() {
2007                        for cap_id in CapabilityId::from_offer_expose(offer)? {
2008                            if !duplicate_check.insert(cap_id.clone()) {
2009                                problem_dictionaries.push(cap_id);
2010                            }
2011                        }
2012                    }
2013                    Ok(())
2014                })?;
2015
2016            if !problem_protocols.is_empty() {
2017                return Err(Error::validate(format!(
2018                    r#"{} {:?} offered to "all" multiple times"#,
2019                    "Protocol(s)",
2020                    problem_protocols.iter().map(|p| format!("{p}")).collect::<Vec<_>>()
2021                )));
2022            }
2023
2024            if !problem_dictionaries.is_empty() {
2025                return Err(Error::validate(format!(
2026                    r#"{} {:?} offered to "all" multiple times"#,
2027                    "Dictionary(s)",
2028                    problem_dictionaries.iter().map(|p| format!("{p}")).collect::<Vec<_>>()
2029                )));
2030            }
2031
2032            let offered_to_all = offers
2033                .iter()
2034                .filter(|o| matches!(o.to, OneOrMany::One(OfferToRef::All)))
2035                .filter(|o| o.protocol.is_some() || o.dictionary.is_some())
2036                .collect::<Vec<&Offer>>();
2037
2038            for offer in offers.iter() {
2039                self.validate_offer(&offer, &mut used_ids, &offered_to_all)?;
2040            }
2041        }
2042
2043        if uses_runner {
2044            // Component "use"s a runner. Ensure we don't also have a runner specified in "program",
2045            // which would necessarily conflict.
2046            self.validate_runner_not_specified(self.document.program.as_ref())?;
2047        } else {
2048            // Component doesn't "use" a runner. Ensure we don't have a component with a "program"
2049            // block which fails to specify a runner.
2050            self.validate_runner_specified(self.document.program.as_ref())?;
2051        }
2052
2053        // Validate "environments".
2054        if let Some(environments) = &self.document.environments {
2055            for env in environments {
2056                self.validate_environment(&env)?;
2057            }
2058        }
2059
2060        // Validate "config"
2061        self.validate_config(&self.document.config)?;
2062
2063        // Check that required offers are present
2064        self.validate_required_offer_decls()?;
2065
2066        // Check that required use decls are present
2067        self.validate_required_use_decls()?;
2068
2069        self.validate_facets()?;
2070
2071        Ok(())
2072    }
2073
2074    fn get_test_facet(&self) -> Option<&serde_json::Value> {
2075        match &self.document.facets {
2076            Some(m) => m.get(TEST_FACET_KEY),
2077            None => None,
2078        }
2079    }
2080
2081    fn validate_facets(&self) -> Result<(), Error> {
2082        let test_facet_map = {
2083            let test_facet = self.get_test_facet();
2084            match &test_facet {
2085                None => None,
2086                Some(serde_json::Value::Object(m)) => Some(m),
2087                Some(facet) => {
2088                    return Err(Error::validate(format!(
2089                        "'{TEST_FACET_KEY}' is not an object: {facet:?}"
2090                    )));
2091                }
2092            }
2093        };
2094
2095        let restrict_test_type = self.features.has(&Feature::RestrictTestTypeInFacet);
2096        let enable_allow_non_hermetic_packages =
2097            self.features.has(&Feature::EnableAllowNonHermeticPackagesFeature);
2098
2099        if restrict_test_type {
2100            let test_type = test_facet_map.map(|m| m.get(TEST_TYPE_FACET_KEY)).flatten();
2101            if test_type.is_some() {
2102                return Err(Error::validate(format!(
2103                    "'{}' is not allowed in facets. Refer \
2104https://fuchsia.dev/fuchsia-src/development/testing/components/test_runner_framework?hl=en#non-hermetic_tests \
2105to run your test in the correct test realm.",
2106                    TEST_TYPE_FACET_KEY
2107                )));
2108            }
2109        }
2110
2111        if enable_allow_non_hermetic_packages {
2112            let allow_non_hermetic_packages = self.features.has(&Feature::AllowNonHermeticPackages);
2113            let deprecated_allowed_packages = test_facet_map
2114                .map_or(false, |m| m.contains_key(TEST_DEPRECATED_ALLOWED_PACKAGES_FACET_KEY));
2115            if deprecated_allowed_packages && !allow_non_hermetic_packages {
2116                return Err(Error::validate(format!(
2117                    "restricted_feature '{}' should be present with facet '{}'",
2118                    Feature::AllowNonHermeticPackages,
2119                    TEST_DEPRECATED_ALLOWED_PACKAGES_FACET_KEY
2120                )));
2121            }
2122            if allow_non_hermetic_packages && !deprecated_allowed_packages {
2123                return Err(Error::validate(format!(
2124                    "Remove restricted_feature '{}' as manifest does not contain facet '{}'",
2125                    Feature::AllowNonHermeticPackages,
2126                    TEST_DEPRECATED_ALLOWED_PACKAGES_FACET_KEY
2127                )));
2128            }
2129        }
2130        Ok(())
2131    }
2132
2133    fn validate_child(&mut self, child: &'a Child) -> Result<(), Error> {
2134        if let Some(resource) = child.url.resource() {
2135            if resource.ends_with(".cml") {
2136                return Err(Error::validate(format!(
2137                    "child URL ends in .cml instead of .cm, \
2138which is almost certainly a mistake: {}",
2139                    child.url
2140                )));
2141            }
2142        }
2143
2144        Ok(())
2145    }
2146
2147    fn validate_collection(&mut self, collection: &'a Collection) -> Result<(), Error> {
2148        if collection.allow_long_names.is_some() {
2149            self.features.check(Feature::AllowLongNames)?;
2150        }
2151        Ok(())
2152    }
2153
2154    fn validate_capability(
2155        &mut self,
2156        capability: &'a Capability,
2157        used_ids: &mut HashMap<String, CapabilityId<'a>>,
2158    ) -> Result<(), Error> {
2159        if capability.directory.is_some() && capability.path.is_none() {
2160            return Err(Error::validate("\"path\" should be present with \"directory\""));
2161        }
2162        if capability.directory.is_some() && capability.rights.is_none() {
2163            return Err(Error::validate("\"rights\" should be present with \"directory\""));
2164        }
2165        if capability.storage.as_ref().is_some() {
2166            if capability.from.is_none() {
2167                return Err(Error::validate("\"from\" should be present with \"storage\""));
2168            }
2169            if capability.path.is_some() {
2170                return Err(Error::validate(
2171                    "\"path\" cannot be present with \"storage\", use \"backing_dir\"",
2172                ));
2173            }
2174            if capability.backing_dir.is_none() {
2175                return Err(Error::validate("\"backing_dir\" should be present with \"storage\""));
2176            }
2177            if capability.storage_id.is_none() {
2178                return Err(Error::validate("\"storage_id\" should be present with \"storage\""));
2179            }
2180        }
2181        if capability.runner.is_some() && capability.from.is_some() {
2182            return Err(Error::validate("\"from\" should not be present with \"runner\""));
2183        }
2184        if capability.runner.is_some() && capability.path.is_none() {
2185            return Err(Error::validate("\"path\" should be present with \"runner\""));
2186        }
2187        if capability.resolver.is_some() && capability.from.is_some() {
2188            return Err(Error::validate("\"from\" should not be present with \"resolver\""));
2189        }
2190        if capability.resolver.is_some() && capability.path.is_none() {
2191            return Err(Error::validate("\"path\" should be present with \"resolver\""));
2192        }
2193
2194        if capability.dictionary.as_ref().is_some() && capability.path.is_some() {
2195            self.features.check(Feature::DynamicDictionaries)?;
2196        }
2197        if capability.delivery.is_some() {
2198            self.features.check(Feature::DeliveryType)?;
2199        }
2200        if let Some(from) = capability.from.as_ref() {
2201            self.validate_component_child_ref("\"capabilities\" source", &AnyRef::from(from))?;
2202        }
2203
2204        // Disallow multiple capability ids of the same name.
2205        let capability_ids = CapabilityId::from_capability(capability)?;
2206        for capability_id in capability_ids {
2207            if used_ids.insert(capability_id.to_string(), capability_id.clone()).is_some() {
2208                return Err(Error::validate(format!(
2209                    "\"{}\" is a duplicate \"capability\" name",
2210                    capability_id,
2211                )));
2212            }
2213        }
2214
2215        Ok(())
2216    }
2217
2218    fn validate_use(
2219        &mut self,
2220        use_: &'a Use,
2221        used_ids: &mut HashMap<String, CapabilityId<'a>>,
2222    ) -> Result<(), Error> {
2223        use_.capability_type()?;
2224        for checker in [
2225            self.service_from_self_checker(use_),
2226            self.protocol_from_self_checker(use_),
2227            self.directory_from_self_checker(use_),
2228            self.config_from_self_checker(use_),
2229        ] {
2230            checker.validate("used")?;
2231        }
2232
2233        if use_.from == Some(UseFromRef::Debug) && use_.protocol.is_none() {
2234            return Err(Error::validate("only \"protocol\" supports source from \"debug\""));
2235        }
2236        if use_.event_stream.is_some() && use_.availability.is_some() {
2237            return Err(Error::validate("\"availability\" cannot be used with \"event_stream\""));
2238        }
2239        if use_.event_stream.is_none() && use_.filter.is_some() {
2240            return Err(Error::validate("\"filter\" can only be used with \"event_stream\""));
2241        }
2242        if use_.storage.is_some() && use_.from.is_some() {
2243            return Err(Error::validate("\"from\" cannot be used with \"storage\""));
2244        }
2245        if use_.runner.is_some() && use_.availability.is_some() {
2246            return Err(Error::validate("\"availability\" cannot be used with \"runner\""));
2247        }
2248        if use_.from == Some(UseFromRef::Self_) && use_.event_stream.is_some() {
2249            return Err(Error::validate("\"from: self\" cannot be used with \"event_stream\""));
2250        }
2251        if use_.from == Some(UseFromRef::Self_) && use_.runner.is_some() {
2252            return Err(Error::validate("\"from: self\" cannot be used with \"runner\""));
2253        }
2254        if use_.availability == Some(Availability::SameAsTarget) {
2255            return Err(Error::validate(
2256                "\"availability: same_as_target\" cannot be used with use declarations",
2257            ));
2258        }
2259        if use_.dictionary.is_some() {
2260            self.features.check(Feature::UseDictionaries)?;
2261        }
2262        if let Some(UseFromRef::Dictionary(_)) = use_.from.as_ref() {
2263            if use_.storage.is_some() {
2264                return Err(Error::validate(
2265                    "Dictionaries do not support \"storage\" capabilities",
2266                ));
2267            }
2268            if use_.event_stream.is_some() {
2269                return Err(Error::validate(
2270                    "Dictionaries do not support \"event_stream\" capabilities",
2271                ));
2272            }
2273        }
2274        if let Some(config) = use_.config.as_ref() {
2275            if use_.key == None {
2276                return Err(Error::validate(format!("Config '{}' missing field 'key'", config)));
2277            }
2278            let _ = use_config_to_value_type(use_)?;
2279            let availability = use_.availability.unwrap_or(Availability::Required);
2280            if availability == Availability::Required && use_.config_default.is_some() {
2281                return Err(Error::validate(format!(
2282                    "Config '{}' is required and has a default value",
2283                    config
2284                )));
2285            }
2286        }
2287        if use_.numbered_handle.as_ref().is_some() {
2288            if use_.protocol.is_some() {
2289                if use_.path.is_some() {
2290                    return Err(Error::validate(format!(
2291                        "`path` and `numbered_handle` are incompatible"
2292                    )));
2293                }
2294            } else {
2295                return Err(Error::validate(format!(
2296                    "`numbered_handle` is only supported for `use protocol`"
2297                )));
2298            }
2299        }
2300
2301        // Disallow multiple capability ids of the same name.
2302        let capability_ids = CapabilityId::from_use(use_)?;
2303        for capability_id in capability_ids {
2304            if let Some(conflicting_capability_id) =
2305                used_ids.insert(capability_id.to_string(), capability_id.clone())
2306            {
2307                if let (CapabilityId::UsedDictionary(_), CapabilityId::UsedDictionary(_)) =
2308                    (&capability_id, &conflicting_capability_id)
2309                {
2310                    // Dictionaries may have conflicting use paths, as they'll be merged together
2311                    // at runtime.
2312                } else {
2313                    return Err(Error::validate(format!(
2314                        "\"{}\" is a duplicate \"use\" target {}",
2315                        capability_id,
2316                        capability_id.type_str()
2317                    )));
2318                }
2319            }
2320            let dir = capability_id.get_dir_path();
2321
2322            // Capability paths must not conflict with `/pkg`, or namespace generation might fail
2323            let pkg_path = cm_types::NamespacePath::new("/pkg").unwrap();
2324            if let Some(ref dir) = dir {
2325                if dir.has_prefix(&pkg_path) {
2326                    return Err(Error::validate(format!(
2327                        "{} \"{}\" conflicts with the protected path \"/pkg\", please use this capability with a different path",
2328                        capability_id.type_str(),
2329                        capability_id,
2330                    )));
2331                }
2332            }
2333
2334            // Validate that paths-based capabilities (service, directory, protocol)
2335            // are not prefixes of each other.
2336            for (_, used_id) in used_ids.iter() {
2337                if capability_id == *used_id {
2338                    continue;
2339                }
2340                let Some(ref path_b) = capability_id.get_target_path() else {
2341                    continue;
2342                };
2343                let Some(path_a) = used_id.get_target_path() else {
2344                    continue;
2345                };
2346                #[derive(Debug, Clone, Copy)]
2347                enum NodeType {
2348                    Service,
2349                    Directory,
2350                    // This variant is never constructed if we're at an API version before "use
2351                    // dictionary" was added.
2352                    #[allow(unused)]
2353                    Dictionary,
2354                }
2355                fn capability_id_to_type(id: &CapabilityId<'_>) -> Option<NodeType> {
2356                    match id {
2357                        CapabilityId::UsedConfiguration(_) => None,
2358                        #[cfg(fuchsia_api_level_at_least = "30")]
2359                        CapabilityId::UsedDictionary(_) => Some(NodeType::Dictionary),
2360                        CapabilityId::UsedDirectory(_) => Some(NodeType::Directory),
2361                        CapabilityId::UsedEventStream(_) => Some(NodeType::Service),
2362                        CapabilityId::UsedProtocol(_) => Some(NodeType::Service),
2363                        #[cfg(fuchsia_api_level_at_least = "HEAD")]
2364                        CapabilityId::UsedRunner(_) => None,
2365                        CapabilityId::UsedService(_) => Some(NodeType::Directory),
2366                        CapabilityId::UsedStorage(_) => Some(NodeType::Directory),
2367                        _ => None,
2368                    }
2369                }
2370                let Some(type_a) = capability_id_to_type(&used_id) else {
2371                    continue;
2372                };
2373                let Some(type_b) = capability_id_to_type(&capability_id) else {
2374                    continue;
2375                };
2376                let mut conflicts = false;
2377                match (type_a, type_b) {
2378                    (NodeType::Service, NodeType::Service)
2379                    | (NodeType::Directory, NodeType::Service)
2380                    | (NodeType::Service, NodeType::Directory)
2381                    | (NodeType::Directory, NodeType::Directory) => {
2382                        if path_a.has_prefix(&path_b) || path_b.has_prefix(&path_a) {
2383                            conflicts = true;
2384                        }
2385                    }
2386                    (NodeType::Dictionary, NodeType::Service)
2387                    | (NodeType::Dictionary, NodeType::Directory) => {
2388                        if path_a.has_prefix(&path_b) {
2389                            conflicts = true;
2390                        }
2391                    }
2392                    (NodeType::Service, NodeType::Dictionary)
2393                    | (NodeType::Directory, NodeType::Dictionary) => {
2394                        if path_b.has_prefix(&path_a) {
2395                            conflicts = true;
2396                        }
2397                    }
2398                    (NodeType::Dictionary, NodeType::Dictionary) => {
2399                        // All combinations of two dictionaries are valid.
2400                    }
2401                }
2402                if conflicts {
2403                    return Err(Error::validate(format!(
2404                        "{} \"{}\" is a prefix of \"use\" target {} \"{}\"",
2405                        used_id.type_str(),
2406                        used_id,
2407                        capability_id.type_str(),
2408                        capability_id,
2409                    )));
2410                }
2411            }
2412        }
2413
2414        if let Some(_) = use_.directory.as_ref() {
2415            // All directory "use" expressions must have directory rights.
2416            match &use_.rights {
2417                Some(rights) => self.validate_directory_rights(&rights)?,
2418                None => {
2419                    return Err(Error::validate(
2420                        "This use statement requires a `rights` field. Refer to: https://fuchsia.dev/go/components/directory#consumer.",
2421                    ));
2422                }
2423            };
2424        }
2425
2426        match (&use_.from, &use_.dependency) {
2427            (Some(UseFromRef::Named(name)), _) if use_.service.is_some() => {
2428                self.validate_component_child_or_collection_ref(
2429                    "\"use\" source",
2430                    &AnyRef::Named(name),
2431                )?;
2432            }
2433            (Some(UseFromRef::Named(name)), _) => {
2434                self.validate_component_child_or_capability_ref(
2435                    "\"use\" source",
2436                    &AnyRef::Named(name),
2437                )?;
2438            }
2439            (
2440                Some(UseFromRef::Dictionary(DictionaryRef {
2441                    path: _,
2442                    root: RootDictionaryRef::Named(name),
2443                })),
2444                _,
2445            ) => {
2446                self.validate_component_child_or_capability_ref(
2447                    "\"use\" source",
2448                    &AnyRef::Named(name),
2449                )?;
2450            }
2451            (_, Some(DependencyType::Weak)) => {
2452                return Err(Error::validate(format!(
2453                    "Only `use` from children can have dependency: \"weak\""
2454                )));
2455            }
2456            _ => {}
2457        }
2458        Ok(())
2459    }
2460
2461    fn validate_expose(
2462        &self,
2463        expose: &'a Expose,
2464        used_ids: &mut HashMap<String, CapabilityId<'a>>,
2465        exposed_to_framework_ids: &mut HashMap<String, CapabilityId<'a>>,
2466    ) -> Result<(), Error> {
2467        expose.capability_type()?;
2468        for checker in [
2469            self.service_from_self_checker(expose),
2470            self.protocol_from_self_checker(expose),
2471            self.directory_from_self_checker(expose),
2472            self.runner_from_self_checker(expose),
2473            self.resolver_from_self_checker(expose),
2474            self.dictionary_from_self_checker(expose),
2475            self.config_from_self_checker(expose),
2476        ] {
2477            checker.validate("exposed")?;
2478        }
2479
2480        // Ensure directory rights are valid.
2481        if let Some(_) = expose.directory.as_ref() {
2482            if expose.from.iter().any(|r| *r == ExposeFromRef::Self_) || expose.rights.is_some() {
2483                if let Some(rights) = expose.rights.as_ref() {
2484                    self.validate_directory_rights(&rights)?;
2485                }
2486            }
2487
2488            // Exposing a subdirectory makes sense for routing but when exposing to framework,
2489            // the subdir should be exposed directly.
2490            if expose.to == Some(ExposeToRef::Framework) {
2491                if expose.subdir.is_some() {
2492                    return Err(Error::validate(
2493                        "`subdir` is not supported for expose to framework. Directly expose the subdirectory instead.",
2494                    ));
2495                }
2496            }
2497        }
2498
2499        if let Some(event_stream) = &expose.event_stream {
2500            if event_stream.iter().len() > 1 && expose.r#as.is_some() {
2501                return Err(Error::validate(format!(
2502                    "as cannot be used with multiple event streams"
2503                )));
2504            }
2505            if let Some(ExposeToRef::Framework) = &expose.to {
2506                return Err(Error::validate(format!("cannot expose an event_stream to framework")));
2507            }
2508            for from in expose.from.iter() {
2509                if from == &ExposeFromRef::Self_ {
2510                    return Err(Error::validate(format!("Cannot expose event_streams from self")));
2511                }
2512            }
2513            if let Some(scopes) = &expose.scope {
2514                for scope in scopes {
2515                    match scope {
2516                        EventScope::Named(name) => {
2517                            if !self.all_children.contains_key(&name.as_ref())
2518                                && !self.all_collections.contains(&name.as_ref())
2519                            {
2520                                return Err(Error::validate(format!(
2521                                    "event_stream scope {} did not match a component or collection in this .cml file.",
2522                                    name.as_str()
2523                                )));
2524                            }
2525                        }
2526                    }
2527                }
2528            }
2529        }
2530
2531        for ref_ in expose.from.iter() {
2532            if let ExposeFromRef::Dictionary(d) = ref_ {
2533                if expose.event_stream.is_some() {
2534                    return Err(Error::validate(
2535                        "Dictionaries do not support \"event_stream\" capabilities",
2536                    ));
2537                }
2538                match &d.root {
2539                    RootDictionaryRef::Self_ | RootDictionaryRef::Named(_) => {}
2540                    RootDictionaryRef::Parent => {
2541                        return Err(Error::validate(
2542                            "`expose` dictionary path must begin with `self` or `#<child-name>`",
2543                        ));
2544                    }
2545                }
2546            }
2547        }
2548
2549        // Ensure we haven't already exposed an entity of the same name.
2550        let capability_ids = CapabilityId::from_offer_expose(expose)?;
2551        for capability_id in capability_ids {
2552            let mut ids = &mut *used_ids;
2553            if expose.to == Some(ExposeToRef::Framework) {
2554                ids = &mut *exposed_to_framework_ids;
2555            }
2556            if ids.insert(capability_id.to_string(), capability_id.clone()).is_some() {
2557                if let CapabilityId::Service(_) = capability_id {
2558                    // Services may have duplicates (aggregation).
2559                } else {
2560                    return Err(Error::validate(format!(
2561                        "\"{}\" is a duplicate \"expose\" target capability for \"{}\"",
2562                        capability_id,
2563                        expose.to.as_ref().unwrap_or(&ExposeToRef::Parent)
2564                    )));
2565                }
2566            }
2567        }
2568
2569        // Validate `from` (done last because this validation depends on the capability type, which
2570        // must be validated first)
2571        self.validate_from_clause(
2572            "expose",
2573            expose,
2574            &expose.source_availability,
2575            &expose.availability,
2576        )?;
2577
2578        Ok(())
2579    }
2580
2581    fn validate_offer(
2582        &mut self,
2583        offer: &'a Offer,
2584        used_ids: &mut HashMap<Name, HashMap<String, CapabilityId<'a>>>,
2585        protocols_offered_to_all: &[&'a Offer],
2586    ) -> Result<(), Error> {
2587        offer.capability_type()?;
2588        for checker in [
2589            self.service_from_self_checker(offer),
2590            self.protocol_from_self_checker(offer),
2591            self.directory_from_self_checker(offer),
2592            self.storage_from_self_checker(offer),
2593            self.runner_from_self_checker(offer),
2594            self.resolver_from_self_checker(offer),
2595            self.dictionary_from_self_checker(offer),
2596            self.config_from_self_checker(offer),
2597        ] {
2598            checker.validate("offered")?;
2599        }
2600
2601        if let Some(stream) = offer.event_stream.as_ref() {
2602            if stream.iter().len() > 1 && offer.r#as.is_some() {
2603                return Err(Error::validate(format!("as cannot be used with multiple events")));
2604            }
2605            for from in &offer.from {
2606                match from {
2607                    OfferFromRef::Self_ => {
2608                        return Err(Error::validate(format!(
2609                            "cannot offer an event_stream from self"
2610                        )));
2611                    }
2612                    _ => {}
2613                }
2614            }
2615        }
2616
2617        // Ensure directory rights are valid.
2618        if let Some(_) = offer.directory.as_ref() {
2619            if offer.from.iter().any(|r| *r == OfferFromRef::Self_) || offer.rights.is_some() {
2620                if let Some(rights) = offer.rights.as_ref() {
2621                    self.validate_directory_rights(&rights)?;
2622                }
2623            }
2624        }
2625
2626        if let Some(storage) = offer.storage.as_ref() {
2627            for storage in storage {
2628                if offer.from.iter().any(|r| r.is_named()) {
2629                    return Err(Error::validate(format!(
2630                        "Storage \"{}\" is offered from a child, but storage capabilities cannot be exposed",
2631                        storage
2632                    )));
2633                }
2634            }
2635        }
2636
2637        for ref_ in offer.from.iter() {
2638            if let OfferFromRef::Dictionary(d) = ref_ {
2639                match &d.root {
2640                    RootDictionaryRef::Self_
2641                    | RootDictionaryRef::Named(_)
2642                    | RootDictionaryRef::Parent => {}
2643                }
2644
2645                if offer.storage.is_some() {
2646                    return Err(Error::validate(
2647                        "Dictionaries do not support \"storage\" capabilities",
2648                    ));
2649                }
2650                if offer.event_stream.is_some() {
2651                    return Err(Error::validate(
2652                        "Dictionaries do not support \"event_stream\" capabilities",
2653                    ));
2654                }
2655            }
2656        }
2657
2658        // Ensure that dependency is set for the right capabilities.
2659        if !offer_can_have_dependency_no_span(offer) && offer.dependency.is_some() {
2660            return Err(Error::validate(
2661                "Dependency can only be provided for protocol, directory, and service capabilities",
2662            ));
2663        }
2664
2665        // Validate every target of this offer.
2666        let target_cap_ids = CapabilityId::from_offer_expose(offer)?;
2667        for to in &offer.to {
2668            // Ensure the "to" value is a child, collection, or dictionary capability.
2669            let to_target = match to {
2670                OfferToRef::All => continue,
2671                OfferToRef::Named(to_target) => {
2672                    // Verify that only a legal set of offers-to-all are made, including that any
2673                    // offer to all duplicated as an offer to a specific component are exactly the same
2674                    for offer_to_all in protocols_offered_to_all {
2675                        offer_to_all_would_duplicate(offer_to_all, offer, to_target)?;
2676                    }
2677
2678                    // Check that any referenced child actually exists.
2679                    // Skip the check if target availability is unknown.
2680                    if offer.target_availability == Some(TargetAvailability::Unknown)
2681                        || self.all_children.contains_key(&to_target.as_ref())
2682                        || self.all_collections.contains(&to_target.as_ref())
2683                    {
2684                        // Allowed.
2685                    } else {
2686                        if let OneOrMany::One(from) = &offer.from {
2687                            return Err(Error::validate(format!(
2688                                "\"{to}\" is an \"offer\" target from \"{from}\" but \"{to}\" does \
2689                                not appear in \"children\" or \"collections\"",
2690                            )));
2691                        } else {
2692                            return Err(Error::validate(format!(
2693                                "\"{to}\" is an \"offer\" target but \"{to}\" does not appear in \
2694                                \"children\" or \"collections\"",
2695                            )));
2696                        }
2697                    }
2698
2699                    // Ensure we are not offering a capability back to its source.
2700                    if let Some(storage) = offer.storage.as_ref() {
2701                        for storage in storage {
2702                            // Storage can only have a single `from` clause and this has been
2703                            // verified.
2704                            if let OneOrMany::One(OfferFromRef::Self_) = &offer.from {
2705                                if let Some(CapabilityFromRef::Named(source)) =
2706                                    self.all_storages.get(&storage.as_ref())
2707                                {
2708                                    if to_target == source {
2709                                        return Err(Error::validate(format!(
2710                                            "Storage offer target \"{}\" is same as source",
2711                                            to
2712                                        )));
2713                                    }
2714                                }
2715                            }
2716                        }
2717                    } else {
2718                        for reference in offer.from.iter() {
2719                            // Weak offers from a child to itself are acceptable.
2720                            if offer_dependency_no_span(offer) == DependencyType::Weak {
2721                                continue;
2722                            }
2723                            match reference {
2724                                OfferFromRef::Named(name) if name == to_target => {
2725                                    return Err(Error::validate(format!(
2726                                        "Offer target \"{}\" is same as source",
2727                                        to
2728                                    )));
2729                                }
2730                                _ => {}
2731                            }
2732                        }
2733                    }
2734                    to_target
2735                }
2736                OfferToRef::OwnDictionary(to_target) => {
2737                    if let Ok(capability_ids) = CapabilityId::from_offer_expose(offer) {
2738                        for id in capability_ids {
2739                            match &id {
2740                                CapabilityId::Protocol(_)
2741                                | CapabilityId::Dictionary(_)
2742                                | CapabilityId::Directory(_)
2743                                | CapabilityId::Runner(_)
2744                                | CapabilityId::Resolver(_)
2745                                | CapabilityId::Service(_)
2746                                | CapabilityId::Configuration(_) => {}
2747                                CapabilityId::Storage(_) | CapabilityId::EventStream(_) => {
2748                                    let type_name = id.type_str();
2749                                    return Err(Error::validate(format!(
2750                                        "\"offer\" to dictionary \"{to}\" for \"{type_name}\" but \
2751                                        dictionaries do not support this type yet."
2752                                    )));
2753                                }
2754                                CapabilityId::UsedService(_)
2755                                | CapabilityId::UsedProtocol(_)
2756                                | CapabilityId::UsedProtocolNumberedHandle(_)
2757                                | CapabilityId::UsedDirectory(_)
2758                                | CapabilityId::UsedStorage(_)
2759                                | CapabilityId::UsedEventStream(_)
2760                                | CapabilityId::UsedRunner(_)
2761                                | CapabilityId::UsedConfiguration(_)
2762                                | CapabilityId::UsedDictionary(_) => {
2763                                    unreachable!("this is not a use")
2764                                }
2765                            }
2766                        }
2767                    }
2768                    // Check that any referenced child actually exists.
2769                    match self.all_dictionaries.get(&to_target.as_ref()) {
2770                        Some(d) => {
2771                            if d.path.is_some() {
2772                                return Err(Error::validate(format!(
2773                                    "\"offer\" has dictionary target \"{to}\" but \"{to_target}\" \
2774                                    sets \"path\". Therefore, it is a dynamic dictionary that \
2775                                    does not allow offers into it."
2776                                )));
2777                            }
2778                        }
2779                        None => {
2780                            if offer.target_availability != Some(TargetAvailability::Unknown) {
2781                                return Err(Error::validate(format!(
2782                                    "\"offer\" has dictionary target \"{to}\" but \"{to_target}\" \
2783                                    is not a dictionary capability defined by this component"
2784                                )));
2785                            }
2786                        }
2787                    }
2788                    to_target
2789                }
2790            };
2791
2792            // Ensure that a target is not offered more than once.
2793            let ids_for_entity = used_ids.entry(to_target.clone()).or_insert(HashMap::new());
2794            for target_cap_id in &target_cap_ids {
2795                if ids_for_entity.insert(target_cap_id.to_string(), target_cap_id.clone()).is_some()
2796                {
2797                    if let CapabilityId::Service(_) = target_cap_id {
2798                        // Services may have duplicates (aggregation).
2799                    } else {
2800                        return Err(Error::validate(format!(
2801                            "\"{}\" is a duplicate \"offer\" target capability for \"{}\"",
2802                            target_cap_id, to
2803                        )));
2804                    }
2805                }
2806            }
2807        }
2808
2809        // Validate `from` (done last because this validation depends on the capability type, which
2810        // must be validated first)
2811        self.validate_from_clause("offer", offer, &offer.source_availability, &offer.availability)?;
2812
2813        Ok(())
2814    }
2815
2816    fn validate_required_offer_decls(&self) -> Result<(), Error> {
2817        let children_stub = Vec::new();
2818        let children = self.document.children.as_ref().unwrap_or(&children_stub);
2819        let collections_stub = Vec::new();
2820        let collections = self.document.collections.as_ref().unwrap_or(&collections_stub);
2821        let offers_stub = Vec::new();
2822        let offers = self.document.offer.as_ref().unwrap_or(&offers_stub);
2823
2824        for required_offer in self.capability_requirements.must_offer {
2825            // for each child, check if any offer is:
2826            //   1) Targeting this child (or all)
2827            //   AND
2828            //   2) Offering the current required capability
2829            for child in children.iter() {
2830                if !offers
2831                    .iter()
2832                    .any(|offer| Self::has_required_offer(offer, &child.name, required_offer))
2833                {
2834                    let capability_type = required_offer.offer_type();
2835                    return Err(Error::validate(format!(
2836                        r#"{capability_type} "{}" is not offered to child component "{}" but it is a required offer"#,
2837                        required_offer.name(),
2838                        child.name
2839                    )));
2840                }
2841            }
2842
2843            for collection in collections.iter() {
2844                if !offers
2845                    .iter()
2846                    .any(|offer| Self::has_required_offer(offer, &collection.name, required_offer))
2847                {
2848                    let capability_type = required_offer.offer_type();
2849                    return Err(Error::validate(format!(
2850                        r#"{capability_type} "{}" is not offered to collection "{}" but it is a required offer"#,
2851                        required_offer.name(),
2852                        collection.name
2853                    )));
2854                }
2855            }
2856        }
2857
2858        Ok(())
2859    }
2860
2861    fn has_required_offer(
2862        offer: &Offer,
2863        target_name: &BorrowedName,
2864        required_offer: &OfferToAllCapability<'_>,
2865    ) -> bool {
2866        let names_this_collection = offer.to.iter().any(|target| match target {
2867            OfferToRef::Named(name) => **name == *target_name,
2868            OfferToRef::All => true,
2869            OfferToRef::OwnDictionary(_) => false,
2870        });
2871        let capability_names = match required_offer {
2872            OfferToAllCapability::Dictionary(_) => offer.dictionary.as_ref(),
2873            OfferToAllCapability::Protocol(_) => offer.protocol.as_ref(),
2874        };
2875        let names_this_capability = match capability_names.as_ref() {
2876            Some(OneOrMany::Many(names)) => {
2877                names.iter().any(|cap_name| cap_name.as_str() == required_offer.name())
2878            }
2879            Some(OneOrMany::One(name)) => {
2880                let cap_name = offer.r#as.as_ref().unwrap_or(name);
2881                cap_name.as_str() == required_offer.name()
2882            }
2883            None => false,
2884        };
2885        names_this_collection && names_this_capability
2886    }
2887
2888    fn validate_required_use_decls(&self) -> Result<(), Error> {
2889        let use_decls_stub = Vec::new();
2890        let use_decls = self.document.r#use.as_ref().unwrap_or(&use_decls_stub);
2891
2892        for required_usage in self.capability_requirements.must_use {
2893            if !use_decls.iter().any(|usage| match usage.protocol.as_ref() {
2894                None => false,
2895                Some(protocol) => protocol
2896                    .iter()
2897                    .any(|protocol_name| protocol_name.as_str() == required_usage.name()),
2898            }) {
2899                return Err(Error::validate(format!(
2900                    r#"Protocol "{}" is not used by a component but is required by all"#,
2901                    required_usage.name(),
2902                )));
2903            }
2904        }
2905
2906        Ok(())
2907    }
2908
2909    /// Validates that the from clause:
2910    ///
2911    /// - is applicable to the capability type,
2912    /// - does not contain duplicates,
2913    /// - references names that exist.
2914    /// - has availability "optional" if the source is "void"
2915    ///
2916    /// `verb` is used in any error messages and is expected to be "offer", "expose", etc.
2917    fn validate_from_clause<T>(
2918        &self,
2919        verb: &str,
2920        cap: &T,
2921        source_availability: &Option<SourceAvailability>,
2922        availability: &Option<Availability>,
2923    ) -> Result<(), Error>
2924    where
2925        T: CapabilityClause + FromClause,
2926    {
2927        let from = cap.from_();
2928        if cap.service().is_none() && from.is_many() {
2929            return Err(Error::validate(format!(
2930                "\"{}\" capabilities cannot have multiple \"from\" clauses",
2931                cap.capability_type().unwrap()
2932            )));
2933        }
2934
2935        if from.is_many() {
2936            ensure_no_duplicate_values(&cap.from_())?;
2937        }
2938
2939        let reference_description = format!("\"{}\" source", verb);
2940        for from_clause in from {
2941            // If this is a protocol, it could reference either a child or a storage capability
2942            // (for the storage admin protocol).
2943            let ref_validity_res = if cap.protocol().is_some() {
2944                self.validate_component_child_or_capability_ref(
2945                    &reference_description,
2946                    &from_clause,
2947                )
2948            } else if cap.service().is_some() {
2949                // Services can also be sourced from collections.
2950                self.validate_component_child_or_collection_ref(
2951                    &reference_description,
2952                    &from_clause,
2953                )
2954            } else {
2955                self.validate_component_child_ref(&reference_description, &from_clause)
2956            };
2957
2958            match ref_validity_res {
2959                Ok(()) if from_clause == AnyRef::Void => {
2960                    // The source is valid and void
2961                    if availability != &Some(Availability::Optional) {
2962                        return Err(Error::validate(format!(
2963                            "capabilities with a source of \"void\" must have an availability of \"optional\", capabilities: \"{}\", from: \"{}\"",
2964                            cap.names().iter().map(|n| n.as_str()).collect::<Vec<_>>().join(", "),
2965                            cap.from_(),
2966                        )));
2967                    }
2968                }
2969                Ok(()) => {
2970                    // The source is valid and not void.
2971                }
2972                Err(_) if source_availability == &Some(SourceAvailability::Unknown) => {
2973                    // The source is invalid, and will be rewritten to void
2974                    if availability != &Some(Availability::Optional) && availability != &None {
2975                        return Err(Error::validate(format!(
2976                            "capabilities with an intentionally missing source must have an availability that is either unset or \"optional\", capabilities: \"{}\", from: \"{}\"",
2977                            cap.names().iter().map(|n| n.as_str()).collect::<Vec<_>>().join(", "),
2978                            cap.from_(),
2979                        )));
2980                    }
2981                }
2982                Err(e) => {
2983                    // The source is invalid, but we're expecting it to be valid.
2984                    return Err(e);
2985                }
2986            }
2987        }
2988        Ok(())
2989    }
2990
2991    /// Validates that the given component exists.
2992    ///
2993    /// - `reference_description` is a human-readable description of the reference used in error
2994    ///   message, such as `"offer" source`.
2995    /// - `component_ref` is a reference to a component. If the reference is a named child, we
2996    ///   ensure that the child component exists.
2997    fn validate_component_child_ref(
2998        &self,
2999        reference_description: &str,
3000        component_ref: &AnyRef<'_>,
3001    ) -> Result<(), Error> {
3002        match component_ref {
3003            AnyRef::Named(name) => {
3004                // Ensure we have a child defined by that name.
3005                if !self.all_children.contains_key(name) {
3006                    return Err(Error::validate(format!(
3007                        "{} \"{}\" does not appear in \"children\"",
3008                        reference_description, component_ref
3009                    )));
3010                }
3011                Ok(())
3012            }
3013            // We don't attempt to validate other reference types.
3014            _ => Ok(()),
3015        }
3016    }
3017
3018    /// Validates that the given component/collection exists.
3019    ///
3020    /// - `reference_description` is a human-readable description of the reference used in error
3021    ///   message, such as `"offer" source`.
3022    /// - `component_ref` is a reference to a component/collection. If the reference is a named
3023    ///   child or collection, we ensure that the child component/collection exists.
3024    fn validate_component_child_or_collection_ref(
3025        &self,
3026        reference_description: &str,
3027        component_ref: &AnyRef<'_>,
3028    ) -> Result<(), Error> {
3029        match component_ref {
3030            AnyRef::Named(name) => {
3031                // Ensure we have a child or collection defined by that name.
3032                if !self.all_children.contains_key(name) && !self.all_collections.contains(name) {
3033                    return Err(Error::validate(format!(
3034                        "{} \"{}\" does not appear in \"children\" or \"collections\"",
3035                        reference_description, component_ref
3036                    )));
3037                }
3038                Ok(())
3039            }
3040            // We don't attempt to validate other reference types.
3041            _ => Ok(()),
3042        }
3043    }
3044
3045    /// Validates that the given capability exists.
3046    ///
3047    /// - `reference_description` is a human-readable description of the reference used in error
3048    ///   message, such as `"offer" source`.
3049    /// - `capability_ref` is a reference to a capability. If the reference is a named capability,
3050    ///   we ensure that the capability exists.
3051    fn validate_component_capability_ref(
3052        &self,
3053        reference_description: &str,
3054        capability_ref: &AnyRef<'_>,
3055    ) -> Result<(), Error> {
3056        match capability_ref {
3057            AnyRef::Named(name) => {
3058                if !self.all_capability_names.contains(name) {
3059                    return Err(Error::validate(format!(
3060                        "{} \"{}\" does not appear in \"capabilities\"",
3061                        reference_description, capability_ref
3062                    )));
3063                }
3064                Ok(())
3065            }
3066            _ => Ok(()),
3067        }
3068    }
3069
3070    /// Validates that the given child component, collection, or capability exists.
3071    ///
3072    /// - `reference_description` is a human-readable description of the reference used in error
3073    ///   message, such as `"offer" source`.
3074    /// - `ref_` is a reference to a child component or capability. If the reference contains a
3075    ///   name, we ensure that a child component or a capability with the name exists.
3076    fn validate_component_child_or_capability_ref(
3077        &self,
3078        reference_description: &str,
3079        ref_: &AnyRef<'_>,
3080    ) -> Result<(), Error> {
3081        if self.validate_component_child_ref(reference_description, ref_).is_err()
3082            && self.validate_component_capability_ref(reference_description, ref_).is_err()
3083        {
3084            return Err(Error::validate(format!(
3085                "{} \"{}\" does not appear in \"children\" or \"capabilities\"",
3086                reference_description, ref_
3087            )));
3088        }
3089        Ok(())
3090    }
3091
3092    /// Validates that directory rights for all route types are valid, i.e that it does not
3093    /// contain duplicate rights.
3094    fn validate_directory_rights(&self, rights_clause: &Rights) -> Result<(), Error> {
3095        let mut rights = HashSet::new();
3096        for right_token in rights_clause.0.iter() {
3097            for right in right_token.expand() {
3098                if !rights.insert(right) {
3099                    return Err(Error::validate(format!(
3100                        "\"{}\" is duplicated in the rights clause.",
3101                        right_token
3102                    )));
3103                }
3104            }
3105        }
3106        Ok(())
3107    }
3108
3109    /// Ensure we don't have a component with a "program" block which fails to specify a runner.
3110    /// This should only be called if the manifest doesn't "use" a runner.
3111    fn validate_runner_specified(&self, program: Option<&Program>) -> Result<(), Error> {
3112        match program {
3113            Some(program) => match program.runner {
3114                Some(_) => Ok(()),
3115                None => {
3116                    return Err(Error::validate(
3117                        "Component has a `program` block defined, but doesn't specify a `runner`. \
3118                        Components need to use a runner to actually execute code.",
3119                    ));
3120                }
3121            },
3122            None => Ok(()),
3123        }
3124    }
3125
3126    /// Ensure we don't have a component with a "program" block which fails to specify a runner.
3127    /// This should only be called if the manifest "use"s a runner.
3128    fn validate_runner_not_specified(&self, program: Option<&Program>) -> Result<(), Error> {
3129        match program {
3130            Some(program) => match program.runner {
3131                Some(_) => {
3132                    // Use/runner always conflicts with program/runner, because use/runner
3133                    // can't be from environment in CML.
3134                    return Err(Error::validate(
3135                        "Component has conflicting runners in `program` block and `use` block.",
3136                    ));
3137                }
3138                None => Ok(()),
3139            },
3140            None => Ok(()),
3141        }
3142    }
3143
3144    fn validate_config(
3145        &self,
3146        fields: &Option<BTreeMap<ConfigKey, ConfigValueType>>,
3147    ) -> Result<(), Error> {
3148        // If we `use` a config capability optionally without a default then it has to exist in the `config` block.
3149        // Collect the names of the keys here.
3150        let optional_use_keys: BTreeMap<ConfigKey, ConfigValueType> = self
3151            .document
3152            .r#use
3153            .iter()
3154            .flatten()
3155            .map(|u| {
3156                if u.config == None {
3157                    return None;
3158                }
3159                if u.availability == Some(Availability::Required) || u.availability == None {
3160                    return None;
3161                }
3162                if let Some(_) = u.config_default.as_ref() {
3163                    return None;
3164                }
3165                let key = ConfigKey(u.key.clone().expect("key should be set").into());
3166                let value = use_config_to_value_type(u).expect("config type should be valid");
3167                Some((key, value))
3168            })
3169            .flatten()
3170            .collect();
3171
3172        let Some(fields) = fields else {
3173            if !optional_use_keys.is_empty() {
3174                return Err(Error::validate(
3175                    "Optionally using a config capability without a default requires a matching 'config' section.",
3176                ));
3177            }
3178            return Ok(());
3179        };
3180
3181        if fields.is_empty() {
3182            return Err(Error::validate("'config' section is empty"));
3183        }
3184
3185        for (key, value) in optional_use_keys {
3186            if !fields.contains_key(&key) {
3187                return Err(Error::validate(format!(
3188                    "'config' section must contain key for optional use '{}'",
3189                    key
3190                )));
3191            }
3192            if fields.get(&key) != Some(&value) {
3193                return Err(Error::validate(format!(
3194                    "Use and config block differ on type for key '{}'",
3195                    key
3196                )));
3197            }
3198        }
3199
3200        Ok(())
3201    }
3202
3203    fn validate_environment(&mut self, environment: &'a Environment) -> Result<(), Error> {
3204        match &environment.extends {
3205            Some(EnvironmentExtends::None) => {
3206                if environment.stop_timeout_ms.is_none() {
3207                    return Err(Error::validate(
3208                        "'__stop_timeout_ms' must be provided if the environment does not extend \
3209                        another environment",
3210                    ));
3211                }
3212            }
3213            Some(EnvironmentExtends::Realm) | None => {}
3214        }
3215
3216        if let Some(runners) = &environment.runners {
3217            let mut used_names = HashMap::new();
3218            for registration in runners {
3219                // Validate that this name is not already used.
3220                let name = registration.r#as.as_ref().unwrap_or(&registration.runner);
3221                if let Some(previous_runner) = used_names.insert(name, &registration.runner) {
3222                    return Err(Error::validate(format!(
3223                        "Duplicate runners registered under name \"{}\": \"{}\" and \"{}\".",
3224                        name, &registration.runner, previous_runner
3225                    )));
3226                }
3227
3228                // Ensure that the environment is defined in `runners` if it comes from `self`.
3229                if registration.from == RegistrationRef::Self_
3230                    && !self.all_runners.contains(&registration.runner.as_ref())
3231                {
3232                    return Err(Error::validate(format!(
3233                        "Runner \"{}\" registered in environment is not in \"runners\"",
3234                        &registration.runner,
3235                    )));
3236                }
3237
3238                self.validate_component_child_ref(
3239                    &format!("\"{}\" runner source", &registration.runner),
3240                    &AnyRef::from(&registration.from),
3241                )?;
3242            }
3243        }
3244
3245        if let Some(resolvers) = &environment.resolvers {
3246            let mut used_schemes = HashMap::new();
3247            for registration in resolvers {
3248                // Validate that the scheme is not already used.
3249                if let Some(previous_resolver) =
3250                    used_schemes.insert(&registration.scheme, &registration.resolver)
3251                {
3252                    return Err(Error::validate(format!(
3253                        "scheme \"{}\" for resolver \"{}\" is already registered; \
3254                        previously registered to resolver \"{}\".",
3255                        &registration.scheme, &registration.resolver, previous_resolver
3256                    )));
3257                }
3258
3259                self.validate_component_child_ref(
3260                    &format!("\"{}\" resolver source", &registration.resolver),
3261                    &AnyRef::from(&registration.from),
3262                )?;
3263            }
3264        }
3265
3266        if let Some(debug_capabilities) = &environment.debug {
3267            for debug in debug_capabilities {
3268                self.protocol_from_self_checker(debug).validate("registered as debug")?;
3269                self.validate_from_clause("debug", debug, &None, &None)?;
3270            }
3271        }
3272        Ok(())
3273    }
3274
3275    fn service_from_self_checker<'b>(
3276        &'b self,
3277        input: &'b (impl CapabilityClause + FromClause),
3278    ) -> RouteFromSelfChecker<'b> {
3279        RouteFromSelfChecker {
3280            capability_name: input.service(),
3281            from: input.from_(),
3282            container: &self.all_services,
3283            all_dictionaries: &self.all_dictionaries,
3284            typename: "service",
3285        }
3286    }
3287
3288    fn protocol_from_self_checker<'b>(
3289        &'b self,
3290        input: &'b (impl CapabilityClause + FromClause),
3291    ) -> RouteFromSelfChecker<'b> {
3292        RouteFromSelfChecker {
3293            capability_name: input.protocol(),
3294            from: input.from_(),
3295            container: &self.all_protocols,
3296            all_dictionaries: &self.all_dictionaries,
3297            typename: "protocol",
3298        }
3299    }
3300
3301    fn directory_from_self_checker<'b>(
3302        &'b self,
3303        input: &'b (impl CapabilityClause + FromClause),
3304    ) -> RouteFromSelfChecker<'b> {
3305        RouteFromSelfChecker {
3306            capability_name: input.directory(),
3307            from: input.from_(),
3308            container: &self.all_directories,
3309            all_dictionaries: &self.all_dictionaries,
3310            typename: "directory",
3311        }
3312    }
3313
3314    fn storage_from_self_checker<'b>(
3315        &'b self,
3316        input: &'b (impl CapabilityClause + FromClause),
3317    ) -> RouteFromSelfChecker<'b> {
3318        RouteFromSelfChecker {
3319            capability_name: input.storage(),
3320            from: input.from_(),
3321            container: &self.all_storages,
3322            all_dictionaries: &self.all_dictionaries,
3323            typename: "storage",
3324        }
3325    }
3326
3327    fn runner_from_self_checker<'b>(
3328        &'b self,
3329        input: &'b (impl CapabilityClause + FromClause),
3330    ) -> RouteFromSelfChecker<'b> {
3331        RouteFromSelfChecker {
3332            capability_name: input.runner(),
3333            from: input.from_(),
3334            container: &self.all_runners,
3335            all_dictionaries: &self.all_dictionaries,
3336            typename: "runner",
3337        }
3338    }
3339
3340    fn resolver_from_self_checker<'b>(
3341        &'b self,
3342        input: &'b (impl CapabilityClause + FromClause),
3343    ) -> RouteFromSelfChecker<'b> {
3344        RouteFromSelfChecker {
3345            capability_name: input.resolver(),
3346            from: input.from_(),
3347            container: &self.all_resolvers,
3348            all_dictionaries: &self.all_dictionaries,
3349            typename: "resolver",
3350        }
3351    }
3352
3353    fn dictionary_from_self_checker<'b>(
3354        &'b self,
3355        input: &'b (impl CapabilityClause + FromClause),
3356    ) -> RouteFromSelfChecker<'b> {
3357        RouteFromSelfChecker {
3358            capability_name: input.dictionary(),
3359            from: input.from_(),
3360            container: &self.all_dictionaries,
3361            all_dictionaries: &self.all_dictionaries,
3362            typename: "dictionary",
3363        }
3364    }
3365
3366    fn config_from_self_checker<'b>(
3367        &'b self,
3368        input: &'b (impl CapabilityClause + FromClause),
3369    ) -> RouteFromSelfChecker<'b> {
3370        RouteFromSelfChecker {
3371            capability_name: input.config(),
3372            from: input.from_(),
3373            container: &self.all_configs,
3374            all_dictionaries: &self.all_dictionaries,
3375            typename: "config",
3376        }
3377    }
3378}
3379
3380/// Helper type that assists with validating declarations of `{use, offer, expose} from self`.
3381struct RouteFromSelfChecker<'a> {
3382    /// The value of the capability property (protocol, service, etc.)
3383    capability_name: Option<OneOrMany<&'a BorrowedName>>,
3384
3385    /// The value of `from`.
3386    from: OneOrMany<AnyRef<'a>>,
3387
3388    /// A [Container] which is used to check for the existence of a capability definition.
3389    container: &'a dyn Container,
3390
3391    /// Reference to [ValidationContext::all_dictionaries].
3392    all_dictionaries: &'a HashMap<&'a BorrowedName, &'a Capability>,
3393
3394    /// The string name for the capability's type.
3395    typename: &'static str,
3396}
3397
3398impl<'a> RouteFromSelfChecker<'a> {
3399    fn validate(self, operand: &'static str) -> Result<(), Error> {
3400        let Self { capability_name, from, container, all_dictionaries, typename } = self;
3401        let Some(capability) = capability_name else {
3402            return Ok(());
3403        };
3404        for capability in capability {
3405            for from in &from {
3406                match from {
3407                    AnyRef::Self_ if !container.contains(capability) => {
3408                        return Err(Error::validate(format!(
3409                            "{typename} \"{capability}\" is {operand} from self, so it \
3410                            must be declared as a \"{typename}\" in \"capabilities\"",
3411                        )));
3412                    }
3413                    AnyRef::Dictionary(DictionaryRef { root: RootDictionaryRef::Self_, path }) => {
3414                        let first_segment = path.iter_segments().next().unwrap();
3415                        if !all_dictionaries.contains_key(first_segment) {
3416                            return Err(Error::validate(format!(
3417                                "{typename} \"{capability}\" is {operand} from \"self/{path}\", so \
3418                                \"{first_segment}\" must be declared as a \"dictionary\" in \"capabilities\"",
3419                            )));
3420                        }
3421                    }
3422                    _ => {}
3423                }
3424            }
3425        }
3426        Ok(())
3427    }
3428}
3429
3430struct RouteFromSelfCheckerV2<'a> {
3431    capability_name: Option<ContextSpanned<OneOrMany<AnyRef<'a>>>>,
3432
3433    from: ContextSpanned<OneOrMany<AnyRef<'a>>>,
3434
3435    container: &'a dyn Container,
3436
3437    all_dictionaries: &'a HashMap<Name, &'a ContextCapability>,
3438
3439    typename: &'static str,
3440}
3441
3442impl<'a> RouteFromSelfCheckerV2<'a> {
3443    fn validate(self, operand: &'static str) -> Result<(), Error> {
3444        let Self { capability_name, from, container, all_dictionaries, typename } = self;
3445
3446        let Some(capability_span) = capability_name else {
3447            return Ok(());
3448        };
3449
3450        for capability in capability_span.value.iter() {
3451            let AnyRef::Named(name_ref) = capability else {
3452                continue;
3453            };
3454
3455            let capability_name = name_ref.as_str();
3456
3457            for from_ref in from.value.iter() {
3458                match from_ref {
3459                    AnyRef::Self_ if !container.contains(name_ref) => {
3460                        return Err(Error::validate_context(
3461                            format!(
3462                                "{typename} \"{capability_name}\" is {operand} from self, so it \
3463                                must be declared as a \"{typename}\" in \"capabilities\"",
3464                            ),
3465                            Some(capability_span.origin.clone()),
3466                        ));
3467                    }
3468
3469                    AnyRef::Dictionary(DictionaryRef { root: RootDictionaryRef::Self_, path }) => {
3470                        let first_segment = path.iter_segments().next().unwrap();
3471                        if !all_dictionaries.contains_key(first_segment) {
3472                            return Err(Error::validate_context(
3473                                format!(
3474                                    "{typename} \"{capability_name}\" is {operand} from \"self/{path}\", so \
3475                                    \"{first_segment}\" must be declared as a \"dictionary\" in \"capabilities\"",
3476                                ),
3477                                Some(from.origin.clone()),
3478                            ));
3479                        }
3480                    }
3481                    _ => {}
3482                }
3483            }
3484        }
3485        Ok(())
3486    }
3487}
3488
3489/// [Container] provides a capability type agnostic trait to check for the existence of a
3490/// capability definition of a particular type. This is useful for writing common validation
3491/// functions.
3492trait Container {
3493    fn contains(&self, key: &BorrowedName) -> bool;
3494}
3495
3496impl<'a> Container for HashSet<&'a BorrowedName> {
3497    fn contains(&self, key: &BorrowedName) -> bool {
3498        self.contains(key)
3499    }
3500}
3501
3502impl Container for HashSet<Name> {
3503    fn contains(&self, key: &BorrowedName) -> bool {
3504        self.contains(key)
3505    }
3506}
3507
3508impl<'a, T> Container for HashMap<&'a BorrowedName, T> {
3509    fn contains(&self, key: &BorrowedName) -> bool {
3510        self.contains_key(key)
3511    }
3512}
3513
3514impl<T> Container for HashMap<Name, T> {
3515    fn contains(&self, key: &BorrowedName) -> bool {
3516        self.contains_key(key)
3517    }
3518}
3519
3520// Construct the config type information out of a `use` for a configuration capability.
3521// This will return validation errors if the `use` is missing fields.
3522pub fn use_config_to_value_type(u: &Use) -> Result<ConfigValueType, Error> {
3523    let config = u.config.clone().expect("Only call use_config_to_value_type on a Config");
3524
3525    let Some(config_type) = u.config_type.as_ref() else {
3526        return Err(Error::validate(format!("Config '{}' is missing field 'type'", config)));
3527    };
3528
3529    let config_type = match config_type {
3530        ConfigType::Bool => ConfigValueType::Bool { mutability: None },
3531        ConfigType::Uint8 => ConfigValueType::Uint8 { mutability: None },
3532        ConfigType::Uint16 => ConfigValueType::Uint16 { mutability: None },
3533        ConfigType::Uint32 => ConfigValueType::Uint32 { mutability: None },
3534        ConfigType::Uint64 => ConfigValueType::Uint64 { mutability: None },
3535        ConfigType::Int8 => ConfigValueType::Int8 { mutability: None },
3536        ConfigType::Int16 => ConfigValueType::Int16 { mutability: None },
3537        ConfigType::Int32 => ConfigValueType::Int32 { mutability: None },
3538        ConfigType::Int64 => ConfigValueType::Int64 { mutability: None },
3539        ConfigType::String => {
3540            let Some(max_size) = u.config_max_size else {
3541                return Err(Error::validate(format!(
3542                    "Config '{}' is type String but is missing field 'max_size'",
3543                    config
3544                )));
3545            };
3546            ConfigValueType::String { max_size: max_size.into(), mutability: None }
3547        }
3548        ConfigType::Vector => {
3549            let Some(ref element) = u.config_element_type else {
3550                return Err(Error::validate(format!(
3551                    "Config '{}' is type Vector but is missing field 'element'",
3552                    config
3553                )));
3554            };
3555            let Some(max_count) = u.config_max_count else {
3556                return Err(Error::validate(format!(
3557                    "Config '{}' is type Vector but is missing field 'max_count'",
3558                    config
3559                )));
3560            };
3561            ConfigValueType::Vector {
3562                max_count: max_count.into(),
3563                element: element.clone(),
3564                mutability: None,
3565            }
3566        }
3567    };
3568    Ok(config_type)
3569}
3570
3571// Construct the config type information out of a `use` for a configuration capability.
3572// This will return validation errors if the `use` is missing fields.
3573pub fn use_config_to_value_type_context(u: &ContextUse) -> Result<ConfigValueType, Error> {
3574    let config = u.config.clone().expect("Only call use_config_to_value_type on a Config");
3575
3576    let Some(config_type) = u.config_type.as_ref() else {
3577        return Err(Error::validate_context(
3578            format!("Config '{}' is missing field 'type'", config.value),
3579            Some(config.origin),
3580        ));
3581    };
3582
3583    let config_type = match config_type.value {
3584        ConfigType::Bool => ConfigValueType::Bool { mutability: None },
3585        ConfigType::Uint8 => ConfigValueType::Uint8 { mutability: None },
3586        ConfigType::Uint16 => ConfigValueType::Uint16 { mutability: None },
3587        ConfigType::Uint32 => ConfigValueType::Uint32 { mutability: None },
3588        ConfigType::Uint64 => ConfigValueType::Uint64 { mutability: None },
3589        ConfigType::Int8 => ConfigValueType::Int8 { mutability: None },
3590        ConfigType::Int16 => ConfigValueType::Int16 { mutability: None },
3591        ConfigType::Int32 => ConfigValueType::Int32 { mutability: None },
3592        ConfigType::Int64 => ConfigValueType::Int64 { mutability: None },
3593        ConfigType::String => {
3594            let Some(ref max_size) = u.config_max_size else {
3595                return Err(Error::validate_context(
3596                    format!(
3597                        "Config '{}' is type String but is missing field 'max_size'",
3598                        config.value
3599                    ),
3600                    Some(config.origin),
3601                ));
3602            };
3603            ConfigValueType::String { max_size: max_size.value.into(), mutability: None }
3604        }
3605        ConfigType::Vector => {
3606            let Some(ref element) = u.config_element_type else {
3607                return Err(Error::validate_context(
3608                    format!(
3609                        "Config '{}' is type Vector but is missing field 'element'",
3610                        config.value
3611                    ),
3612                    Some(config.origin),
3613                ));
3614            };
3615            let Some(ref max_count) = u.config_max_count else {
3616                return Err(Error::validate_context(
3617                    format!(
3618                        "Config '{}' is type Vector but is missing field 'max_count'",
3619                        config.value
3620                    ),
3621                    Some(config.origin),
3622                ));
3623            };
3624            ConfigValueType::Vector {
3625                max_count: max_count.value.into(),
3626                element: element.value.clone(),
3627                mutability: None,
3628            }
3629        }
3630    };
3631    Ok(config_type)
3632}
3633
3634/// Given an iterator with `(key, name)` tuples, ensure that `key` doesn't
3635/// appear twice. `name` is used in generated error messages.
3636fn ensure_no_duplicate_names<'a, I>(values: I) -> Result<(), Error>
3637where
3638    I: Iterator<Item = (&'a BorrowedName, &'a str)>,
3639{
3640    let mut seen_keys = HashMap::new();
3641    for (key, name) in values {
3642        if let Some(preexisting_name) = seen_keys.insert(key, name) {
3643            return Err(Error::validate(format!(
3644                "identifier \"{}\" is defined twice, once in \"{}\" and once in \"{}\"",
3645                key, name, preexisting_name
3646            )));
3647        }
3648    }
3649    Ok(())
3650}
3651
3652/// Returns an error if the iterator contains duplicate values.
3653fn ensure_no_duplicate_values<'a, I, V>(values: I) -> Result<(), Error>
3654where
3655    I: IntoIterator<Item = &'a V>,
3656    V: 'a + Hash + Eq + fmt::Display,
3657{
3658    let mut seen = HashSet::new();
3659    for value in values {
3660        if !seen.insert(value) {
3661            return Err(Error::validate(format!("Found duplicate value \"{}\" in array.", value)));
3662        }
3663    }
3664    Ok(())
3665}
3666
3667#[cfg(test)]
3668mod tests {
3669    use super::*;
3670    use crate::error::Location;
3671    use crate::types::offer::{
3672        offer_to_all_and_component_diff_capabilities_message,
3673        offer_to_all_and_component_diff_sources_message,
3674    };
3675    use assert_matches::assert_matches;
3676    use serde_json::json;
3677
3678    macro_rules! test_validate_cml {
3679        (
3680            $(
3681                $test_name:ident($input:expr, $($pattern:tt)+),
3682            )+
3683        ) => {
3684            $(
3685                #[test]
3686                fn $test_name() {
3687                    let input = format!("{}", $input);
3688                    let result = validate_for_test("test.cml", &input.as_bytes());
3689                    assert_matches!(result, $($pattern)+);
3690                }
3691            )+
3692        }
3693    }
3694
3695    macro_rules! test_validate_cml_with_context {
3696        (
3697            $(
3698                $test_name:ident($input:expr, $($pattern:tt)+),
3699            )+
3700        ) => {
3701            $(
3702                #[test]
3703                fn $test_name() {
3704                    let input = format!("{}", $input);
3705                    let result = validate_for_test_context("test.cml", &input.as_bytes());
3706                    assert_matches!(result, $($pattern)+);
3707                }
3708            )+
3709        }
3710    }
3711
3712    macro_rules! test_validate_cml_with_feature {
3713        (
3714            $features:expr,
3715            {
3716                $(
3717                    $test_name:ident($input:expr, $($pattern:tt)+),
3718                )+
3719            }
3720        ) => {
3721            $(
3722                #[test]
3723                fn $test_name() {
3724                    let input = format!("{}", $input);
3725                    let features = $features;
3726                    let result = validate_with_features_for_test("test.cml", &input.as_bytes(), &features, &vec![], &vec![], &vec![]);
3727                    assert_matches!(result, $($pattern)+);
3728                }
3729            )+
3730        }
3731    }
3732
3733    macro_rules! test_validate_cml_with_feature_context {
3734        (
3735            $features:expr,
3736            {
3737                $(
3738                    $test_name:ident($input:expr, $($pattern:tt)+),
3739                )+
3740            }
3741        ) => {
3742            $(
3743                #[test]
3744                fn $test_name() {
3745                    let input = format!("{}", $input);
3746                    let features = $features;
3747                    let result = validate_with_features_for_test_context("test.cml", &input.as_bytes(), &features, &vec![], &vec![], &vec![]);
3748                    assert_matches!(result, $($pattern)+);
3749                }
3750            )+
3751        }
3752    }
3753
3754    fn validate_for_test(filename: &str, input: &[u8]) -> Result<(), Error> {
3755        validate_with_features_for_test(filename, input, &FeatureSet::empty(), &[], &[], &[])
3756    }
3757
3758    fn validate_for_test_context(filename: &str, input: &[u8]) -> Result<(), Error> {
3759        validate_with_features_for_test_context(
3760            filename,
3761            input,
3762            &FeatureSet::empty(),
3763            &[],
3764            &[],
3765            &[],
3766        )
3767    }
3768
3769    fn validate_with_features_for_test_context(
3770        filename: &str,
3771        input: &[u8],
3772        features: &FeatureSet,
3773        required_offers: &[String],
3774        required_uses: &[String],
3775        required_dictionary_offers: &[String],
3776    ) -> Result<(), Error> {
3777        let input = format!("{}", std::str::from_utf8(input).unwrap().to_string());
3778        let file = Path::new(filename);
3779        let document = crate::load_cml_with_context(&input, file)?;
3780        validate_cml_context(
3781            &document,
3782            &features,
3783            &CapabilityRequirements {
3784                must_offer: &required_offers
3785                    .iter()
3786                    .map(|value| OfferToAllCapability::Protocol(value))
3787                    .chain(
3788                        required_dictionary_offers
3789                            .iter()
3790                            .map(|value| OfferToAllCapability::Dictionary(value)),
3791                    )
3792                    .collect::<Vec<_>>(),
3793                must_use: &required_uses
3794                    .iter()
3795                    .map(|value| MustUseRequirement::Protocol(value))
3796                    .collect::<Vec<_>>(),
3797            },
3798        )
3799    }
3800
3801    fn validate_with_features_for_test(
3802        filename: &str,
3803        input: &[u8],
3804        features: &FeatureSet,
3805        required_offers: &[String],
3806        required_uses: &[String],
3807        required_dictionary_offers: &[String],
3808    ) -> Result<(), Error> {
3809        let input = format!("{}", std::str::from_utf8(input).unwrap().to_string());
3810        let file = Path::new(filename);
3811        let document = crate::parse_one_document(&input, &file)?;
3812        validate_cml(
3813            &document,
3814            Some(&file),
3815            &features,
3816            &CapabilityRequirements {
3817                must_offer: &required_offers
3818                    .iter()
3819                    .map(|value| OfferToAllCapability::Protocol(value))
3820                    .chain(
3821                        required_dictionary_offers
3822                            .iter()
3823                            .map(|value| OfferToAllCapability::Dictionary(value)),
3824                    )
3825                    .collect::<Vec<_>>(),
3826                must_use: &required_uses
3827                    .iter()
3828                    .map(|value| MustUseRequirement::Protocol(value))
3829                    .collect::<Vec<_>>(),
3830            },
3831        )
3832    }
3833
3834    fn unused_component_err_message(missing: &str) -> String {
3835        format!(r#"Protocol "{}" is not used by a component but is required by all"#, missing)
3836    }
3837
3838    #[test]
3839    fn must_use_protocol() {
3840        let input = r##"{
3841            children: [
3842                {
3843                    name: "logger",
3844                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
3845                },
3846                {
3847                    name: "something",
3848                    url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
3849                },
3850            ],
3851        }"##;
3852
3853        let result = validate_with_features_for_test(
3854            "test.cml",
3855            input.as_bytes(),
3856            &FeatureSet::empty(),
3857            &[],
3858            &vec!["fuchsia.logger.LogSink".into()],
3859            &[],
3860        );
3861
3862        assert_matches!(result,
3863            Err(Error::Validate { err, filename }) => {
3864                assert_eq!(err, unused_component_err_message("fuchsia.logger.LogSink"));
3865                assert!(filename.is_some(), "Expected there to be a filename in error message");
3866            }
3867        );
3868
3869        let input = r##"{
3870            children: [
3871                {
3872                    name: "logger",
3873                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
3874                },
3875            ],
3876
3877            use: [
3878                {
3879                    protocol: [ "fuchsia.component.Binder" ],
3880                    from: "framework",
3881                }
3882            ],
3883        }"##;
3884
3885        let result = validate_with_features_for_test(
3886            "test.cml",
3887            input.as_bytes(),
3888            &FeatureSet::empty(),
3889            &[],
3890            &vec!["fuchsia.component.Binder".into()],
3891            &[],
3892        );
3893        assert_matches!(result, Ok(_));
3894    }
3895
3896    #[test]
3897    fn required_offer_to_all() {
3898        let input = r##"{
3899           children: [
3900               {
3901                   name: "logger",
3902                   url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
3903               },
3904               {
3905                   name: "something",
3906                   url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
3907               },
3908           ],
3909           collections: [
3910               {
3911                   name: "coll",
3912                   durability: "transient",
3913               },
3914           ],
3915           offer: [
3916               {
3917                   protocol: "fuchsia.logger.LogSink",
3918                   from: "parent",
3919                   to: "all"
3920               },
3921               {
3922                   protocol: "fuchsia.inspect.InspectSink",
3923                   from: "parent",
3924                   to: "all"
3925               },
3926               {
3927                   protocol: "fuchsia.process.Launcher",
3928                   from: "parent",
3929                   to: "#something",
3930               },
3931           ]
3932       }"##;
3933        let result = validate_with_features_for_test(
3934            "test.cml",
3935            input.as_bytes(),
3936            &FeatureSet::empty(),
3937            &vec!["fuchsia.logger.LogSink".into(), "fuchsia.inspect.InspectSink".into()],
3938            &Vec::new(),
3939            &[],
3940        );
3941        assert_matches!(result, Ok(_));
3942    }
3943
3944    #[test]
3945    fn required_offer_to_all_manually() {
3946        let input = r##"{
3947            children: [
3948                {
3949                    name: "logger",
3950                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
3951                },
3952                {
3953                    name: "something",
3954                    url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
3955                },
3956            ],
3957            collections: [
3958                {
3959                    name: "coll",
3960                    durability: "transient",
3961                },
3962            ],
3963            offer: [
3964                {
3965                    protocol: "fuchsia.logger.LogSink",
3966                    from: "#something",
3967                    to: "#logger"
3968                },
3969                {
3970                    protocol: "fuchsia.logger.LogSink",
3971                    from: "parent",
3972                    to: "#something"
3973                },
3974                {
3975                    protocol: "fuchsia.logger.LogSink",
3976                    from: "parent",
3977                    to: "#coll",
3978                },
3979            ]
3980        }"##;
3981        let result = validate_with_features_for_test(
3982            "test.cml",
3983            input.as_bytes(),
3984            &FeatureSet::empty(),
3985            &vec!["fuchsia.logger.LogSink".into()],
3986            &[],
3987            &[],
3988        );
3989        assert_matches!(result, Ok(_));
3990
3991        let input = r##"{
3992            children: [
3993                {
3994                    name: "logger",
3995                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
3996                },
3997                {
3998                    name: "something",
3999                    url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
4000                },
4001                {
4002                    name: "something_v2",
4003                    url: "fuchsia-pkg://fuchsia.com/something_v2#meta/something_v2.cm",
4004                },
4005            ],
4006            collections: [
4007                {
4008                    name: "coll",
4009                    durability: "transient",
4010                },
4011            ],
4012            offer: [
4013                {
4014                    protocol: "fuchsia.logger.LogSink",
4015                    from: "parent",
4016                    to: ["#logger", "#something", "#something_v2", "#coll"],
4017                },
4018            ]
4019        }"##;
4020        let result = validate_with_features_for_test(
4021            "test.cml",
4022            input.as_bytes(),
4023            &FeatureSet::empty(),
4024            &vec!["fuchsia.logger.LogSink".into()],
4025            &[],
4026            &[],
4027        );
4028        assert_matches!(result, Ok(_));
4029    }
4030
4031    #[test]
4032    fn offer_to_all_mixed_with_array_syntax() {
4033        let input = r##"{
4034                "children": [
4035                    {
4036                        "name": "something",
4037                        "url": "fuchsia-pkg://fuchsia.com/something/stable#meta/something.cm",
4038                    },
4039                ],
4040                "offer": [
4041                    {
4042                        "protocol": ["fuchsia.logger.LogSink", "fuchsia.inspect.InspectSink",],
4043                        "from": "parent",
4044                        "to": "#something",
4045                    },
4046                    {
4047                        "protocol": "fuchsia.logger.LogSink",
4048                        "from": "parent",
4049                        "to": "all",
4050                    },
4051                ],
4052        }"##;
4053
4054        let result = validate_with_features_for_test(
4055            "test.cml",
4056            input.as_bytes(),
4057            &FeatureSet::empty(),
4058            &vec!["fuchsia.logger.LogSink".into()],
4059            &Vec::new(),
4060            &[],
4061        );
4062
4063        assert_matches!(result, Ok(_));
4064
4065        let input = r##"{
4066
4067                "children": [
4068                    {
4069                        "name": "something",
4070                        "url": "fuchsia-pkg://fuchsia.com/something/stable#meta/something.cm",
4071                    },
4072                ],
4073                "offer": [
4074                    {
4075                        "protocol": ["fuchsia.logger.LogSink", "fuchsia.inspect.InspectSink",],
4076                        "from": "parent",
4077                        "to": "all",
4078                    },
4079                    {
4080                        "protocol": "fuchsia.logger.LogSink",
4081                        "from": "parent",
4082                        "to": "#something",
4083                    },
4084                ],
4085        }"##;
4086
4087        let result = validate_with_features_for_test(
4088            "test.cml",
4089            input.as_bytes(),
4090            &FeatureSet::empty(),
4091            &vec!["fuchsia.logger.LogSink".into()],
4092            &Vec::new(),
4093            &[],
4094        );
4095
4096        assert_matches!(result, Ok(_));
4097    }
4098
4099    #[test]
4100    fn offer_to_all_and_manual() {
4101        let input = r##"{
4102            children: [
4103                {
4104                    name: "logger",
4105                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
4106                },
4107                {
4108                    name: "something",
4109                    url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
4110                },
4111            ],
4112            offer: [
4113                {
4114                    protocol: "fuchsia.logger.LogSink",
4115                    from: "parent",
4116                    to: "all"
4117                },
4118                {
4119                    protocol: "fuchsia.logger.LogSink",
4120                    from: "parent",
4121                    to: "#something"
4122                },
4123            ]
4124        }"##;
4125
4126        let result = validate_with_features_for_test(
4127            "test.cml",
4128            input.as_bytes(),
4129            &FeatureSet::empty(),
4130            &vec!["fuchsia.logger.LogSink".into()],
4131            &Vec::new(),
4132            &[],
4133        );
4134
4135        // exact duplication is allowed
4136        assert_matches!(result, Ok(_));
4137
4138        let input = r##"{
4139            children: [
4140                {
4141                    name: "logger",
4142                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
4143                },
4144                {
4145                    name: "something",
4146                    url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
4147                },
4148            ],
4149            offer: [
4150                {
4151                    protocol: "fuchsia.logger.LogSink",
4152                    from: "parent",
4153                    to: "all"
4154                },
4155                {
4156                    protocol: "fuchsia.logger.FakLog",
4157                    from: "parent",
4158                    as: "fuchsia.logger.LogSink",
4159                    to: "#something"
4160                },
4161            ]
4162        }"##;
4163
4164        let result = validate_with_features_for_test(
4165            "test.cml",
4166            input.as_bytes(),
4167            &FeatureSet::empty(),
4168            &vec!["fuchsia.logger.LogSink".into()],
4169            &Vec::new(),
4170            &[],
4171        );
4172
4173        // aliased duplications are forbidden
4174        assert_matches!(result,
4175            Err(Error::Validate { err, filename }) => {
4176                assert_eq!(
4177                    err,
4178                    offer_to_all_and_component_diff_capabilities_message([OfferToAllCapability::Protocol("fuchsia.logger.LogSink")].into_iter(), "something"),
4179                );
4180                assert!(filename.is_some(), "Expected there to be a filename in error message");
4181            }
4182        );
4183
4184        let input = r##"{
4185            children: [
4186                {
4187                    name: "logger",
4188                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
4189                },
4190                {
4191                    name: "something",
4192                    url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
4193                },
4194            ],
4195            offer: [
4196                {
4197                    protocol: "fuchsia.logger.LogSink",
4198                    from: "parent",
4199                    to: "all"
4200                },
4201                {
4202                    protocol: "fuchsia.logger.LogSink",
4203                    from: "framework",
4204                    to: "#something"
4205                },
4206            ]
4207        }"##;
4208
4209        let result = validate_with_features_for_test(
4210            "test.cml",
4211            input.as_bytes(),
4212            &FeatureSet::empty(),
4213            &vec!["fuchsia.logger.LogSink".into()],
4214            &Vec::new(),
4215            &[],
4216        );
4217
4218        // offering the same protocol without an alias from different sources is forbidden
4219        assert_matches!(result,
4220            Err(Error::Validate { err, filename }) => {
4221                assert_eq!(
4222                    err,
4223                    offer_to_all_and_component_diff_sources_message([OfferToAllCapability::Protocol("fuchsia.logger.LogSink")].into_iter(), "something"),
4224                );
4225                assert!(filename.is_some(), "Expected there to be a filename in error message");
4226            }
4227        );
4228    }
4229
4230    #[test]
4231    fn offer_to_all_and_manual_for_dictionary() {
4232        let input = r##"{
4233            children: [
4234                {
4235                    name: "logger",
4236                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
4237                },
4238                {
4239                    name: "something",
4240                    url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
4241                },
4242            ],
4243            offer: [
4244                {
4245                    dictionary: "diagnostics",
4246                    from: "parent",
4247                    to: "all"
4248                },
4249                {
4250                    dictionary: "diagnostics",
4251                    from: "parent",
4252                    to: "#something"
4253                },
4254            ]
4255        }"##;
4256
4257        let result = validate_with_features_for_test(
4258            "test.cml",
4259            input.as_bytes(),
4260            &FeatureSet::empty(),
4261            &vec![],
4262            &Vec::new(),
4263            &["diagnostics".into()],
4264        );
4265
4266        // exact duplication is allowed
4267        assert_matches!(result, Ok(_));
4268
4269        let input = r##"{
4270            children: [
4271                {
4272                    name: "logger",
4273                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
4274                },
4275                {
4276                    name: "something",
4277                    url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
4278                },
4279            ],
4280            offer: [
4281                {
4282                    dictionary: "diagnostics",
4283                    from: "parent",
4284                    to: "all"
4285                },
4286                {
4287                    dictionary: "FakDictionary",
4288                    from: "parent",
4289                    as: "diagnostics",
4290                    to: "#something"
4291                },
4292            ]
4293        }"##;
4294
4295        let result = validate_with_features_for_test(
4296            "test.cml",
4297            input.as_bytes(),
4298            &FeatureSet::empty(),
4299            &vec![],
4300            &Vec::new(),
4301            &["diagnostics".into()],
4302        );
4303
4304        // aliased duplications are forbidden
4305        assert_matches!(result,
4306            Err(Error::Validate { err, filename }) => {
4307                assert_eq!(
4308                    err,
4309                    offer_to_all_and_component_diff_capabilities_message([OfferToAllCapability::Dictionary("diagnostics")].into_iter(), "something"),
4310                );
4311                assert!(filename.is_some(), "Expected there to be a filename in error message");
4312            }
4313        );
4314
4315        let input = r##"{
4316            children: [
4317                {
4318                    name: "logger",
4319                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
4320                },
4321                {
4322                    name: "something",
4323                    url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
4324                },
4325            ],
4326            offer: [
4327                {
4328                    dictionary: "diagnostics",
4329                    from: "parent",
4330                    to: "all"
4331                },
4332                {
4333                    dictionary: "diagnostics",
4334                    from: "framework",
4335                    to: "#something"
4336                },
4337            ]
4338        }"##;
4339
4340        let result = validate_with_features_for_test(
4341            "test.cml",
4342            input.as_bytes(),
4343            &FeatureSet::empty(),
4344            &vec![],
4345            &Vec::new(),
4346            &["diagnostics".into()],
4347        );
4348
4349        // offering the same dictionary without an alias from different sources is forbidden
4350        assert_matches!(result,
4351            Err(Error::Validate { err, filename }) => {
4352                assert_eq!(
4353                    err,
4354                    offer_to_all_and_component_diff_sources_message([OfferToAllCapability::Dictionary("diagnostics")].into_iter(), "something"),
4355                );
4356                assert!(filename.is_some(), "Expected there to be a filename in error message");
4357            }
4358        );
4359    }
4360
4361    fn offer_to_all_diff_sources_message(protocols: &[&str]) -> String {
4362        format!(r#"Protocol(s) {:?} offered to "all" multiple times"#, protocols)
4363    }
4364
4365    #[test]
4366    fn offer_to_all_from_diff_sources() {
4367        let input = r##"{
4368            "children": [
4369                {
4370                    "name": "logger",
4371                    "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
4372                },
4373                {
4374                    "name": "something",
4375                    "url": "fuchsia-pkg://fuchsia.com/something#meta/something.cm"
4376                }
4377            ],
4378            "offer": [
4379                {
4380                    "protocol": "fuchsia.logger.LogSink",
4381                    "from": "parent",
4382                    "to": "all"
4383                },
4384                {
4385                    "protocol": "fuchsia.logger.LogSink",
4386                    "from": "framework",
4387                    "to": "all"
4388                }
4389            ]
4390        }"##;
4391
4392        let result = validate_with_features_for_test_context(
4393            "test.cml",
4394            input.as_bytes(),
4395            &FeatureSet::empty(),
4396            &vec!["fuchsia.logger.LogSink".into()],
4397            &Vec::new(),
4398            &[],
4399        );
4400
4401        assert_matches!(result,
4402            Err(Error::ValidateContexts { err, .. }) => {
4403                assert_eq!(
4404                    err,
4405                    offer_to_all_diff_sources_message(&["fuchsia.logger.LogSink"]),
4406                );
4407            }
4408        );
4409    }
4410
4411    #[test]
4412    fn offer_to_all_from_diff_sources_no_span() {
4413        let input = r##"{
4414            children: [
4415                {
4416                    name: "logger",
4417                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
4418                },
4419                {
4420                    name: "something",
4421                    url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
4422                },
4423            ],
4424            offer: [
4425                {
4426                    protocol: "fuchsia.logger.LogSink",
4427                    from: "parent",
4428                    to: "all"
4429                },
4430                {
4431                    protocol: "fuchsia.logger.LogSink",
4432                    from: "framework",
4433                    to: "all"
4434                },
4435            ]
4436        }"##;
4437
4438        let result = validate_with_features_for_test(
4439            "test.cml",
4440            input.as_bytes(),
4441            &FeatureSet::empty(),
4442            &vec!["fuchsia.logger.LogSink".into()],
4443            &Vec::new(),
4444            &[],
4445        );
4446
4447        assert_matches!(result,
4448            Err(Error::Validate { err, filename }) => {
4449                assert_eq!(
4450                    err,
4451                    offer_to_all_diff_sources_message(&["fuchsia.logger.LogSink"]),
4452                );
4453                assert!(filename.is_some(), "Expected there to be a filename in error message");
4454            }
4455        );
4456    }
4457
4458    #[test]
4459    fn offer_to_all_with_aliases() {
4460        let input = r##"{
4461            children: [
4462                {
4463                    name: "logger",
4464                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
4465                },
4466                {
4467                    name: "something",
4468                    url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
4469                },
4470            ],
4471            offer: [
4472                {
4473                    protocol: "fuchsia.logger.LogSink",
4474                    from: "parent",
4475                    to: "all"
4476                },
4477                {
4478                    protocol: "fuchsia.logger.LogSink",
4479                    from: "framework",
4480                    to: "all",
4481                    as: "OtherLogSink",
4482                },
4483                {
4484                    protocol: "fuchsia.logger.LogSink",
4485                    from: "framework",
4486                    to: "#something",
4487                    as: "OtherOtherLogSink",
4488                },
4489                {
4490                    protocol: "fuchsia.logger.LogSink",
4491                    from: "parent",
4492                    to: "#something",
4493                    as: "fuchsia.logger.LogSink",
4494                },
4495            ]
4496        }"##;
4497
4498        let result = validate_with_features_for_test(
4499            "test.cml",
4500            input.as_bytes(),
4501            &FeatureSet::empty(),
4502            &["fuchsia.logger.LogSink".into()],
4503            &[],
4504            &[],
4505        );
4506
4507        assert_matches!(result, Ok(_));
4508    }
4509
4510    #[test]
4511    fn required_dict_offers_accept_aliases() {
4512        let input = r##"{
4513            capabilities: [
4514                {
4515                    dictionary: "test-diagnostics",
4516                }
4517            ],
4518            children: [
4519                {
4520                    name: "something",
4521                    url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
4522                },
4523            ],
4524            offer: [
4525                {
4526                    dictionary: "test-diagnostics",
4527                    from: "self",
4528                    to: "#something",
4529                    as: "diagnostics",
4530                }
4531            ]
4532        }"##;
4533
4534        let result = validate_with_features_for_test(
4535            "test.cml",
4536            input.as_bytes(),
4537            &FeatureSet::empty(),
4538            &[],
4539            &[],
4540            &["diagnostics".into()],
4541        );
4542
4543        assert_matches!(result, Ok(_));
4544    }
4545
4546    fn fail_to_make_required_offer(
4547        protocol: &str,
4548        child_or_collection: &str,
4549        component: &str,
4550    ) -> String {
4551        format!(
4552            r#"Protocol "{}" is not offered to {} "{}" but it is a required offer"#,
4553            protocol, child_or_collection, component
4554        )
4555    }
4556
4557    fn fail_to_make_required_offer_dictionary(
4558        dictionary: &str,
4559        child_or_collection: &str,
4560        component: &str,
4561    ) -> String {
4562        format!(
4563            r#"Dictionary "{}" is not offered to {} "{}" but it is a required offer"#,
4564            dictionary, child_or_collection, component
4565        )
4566    }
4567
4568    #[test]
4569    fn fail_to_offer_to_all_when_required() {
4570        let input = r##"{
4571            children: [
4572                {
4573                    name: "logger",
4574                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
4575                },
4576                {
4577                    name: "something",
4578                    url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
4579                },
4580            ],
4581            offer: [
4582                {
4583                    protocol: "fuchsia.logger.LogSink",
4584                    from: "parent",
4585                    to: "#logger"
4586                },
4587                {
4588                    protocol: "fuchsia.logger.LegacyLog",
4589                    from: "parent",
4590                    to: "#something"
4591                },
4592            ]
4593        }"##;
4594        let result = validate_with_features_for_test(
4595            "test.cml",
4596            input.as_bytes(),
4597            &FeatureSet::empty(),
4598            &vec!["fuchsia.logger.LogSink".into()],
4599            &[],
4600            &[],
4601        );
4602
4603        assert_matches!(result,
4604            Err(Error::Validate { err, filename }) => {
4605                assert_eq!(
4606                    err,
4607                    fail_to_make_required_offer(
4608                        "fuchsia.logger.LogSink",
4609                        "child component",
4610                        "something",
4611                    ),
4612                );
4613                assert!(filename.is_some(), "Expected there to be a filename in error message");
4614            }
4615        );
4616
4617        let input = r##"{
4618            children: [
4619                {
4620                    name: "logger",
4621                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
4622                },
4623            ],
4624            collections: [
4625                {
4626                    name: "coll",
4627                    durability: "transient",
4628                },
4629            ],
4630            offer: [
4631                {
4632                    protocol: "fuchsia.logger.LogSink",
4633                    from: "parent",
4634                    to: "#logger"
4635                },
4636            ]
4637        }"##;
4638        let result = validate_with_features_for_test(
4639            "test.cml",
4640            input.as_bytes(),
4641            &FeatureSet::empty(),
4642            &vec!["fuchsia.logger.LogSink".into()],
4643            &[],
4644            &[],
4645        );
4646
4647        assert_matches!(result,
4648            Err(Error::Validate { err, filename }) => {
4649                assert_eq!(
4650                    err,
4651                    fail_to_make_required_offer("fuchsia.logger.LogSink", "collection", "coll"),
4652                );
4653                assert!(filename.is_some(), "Expected there to be a filename in error message");
4654            }
4655        );
4656    }
4657
4658    #[test]
4659    fn fail_to_offer_dictionary_to_all_when_required() {
4660        let input = r##"{
4661            children: [
4662                {
4663                    name: "logger",
4664                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
4665                },
4666                {
4667                    name: "something",
4668                    url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
4669                },
4670            ],
4671            offer: [
4672                {
4673                    protocol: "fuchsia.logger.LogSink",
4674                    from: "parent",
4675                    to: "all"
4676                },
4677                {
4678                    dictionary: "diagnostics",
4679                    from: "parent",
4680                    to: "#logger"
4681                },
4682                {
4683                    protocol: "fuchsia.logger.LegacyLog",
4684                    from: "parent",
4685                    to: "#something"
4686                },
4687            ]
4688        }"##;
4689        let result = validate_with_features_for_test(
4690            "test.cml",
4691            input.as_bytes(),
4692            &FeatureSet::empty(),
4693            &vec![],
4694            &[],
4695            &["diagnostics".to_string()],
4696        );
4697
4698        assert_matches!(result,
4699            Err(Error::Validate { err, filename }) => {
4700                assert_eq!(
4701                    err,
4702                    fail_to_make_required_offer_dictionary(
4703                        "diagnostics",
4704                        "child component",
4705                        "something",
4706                    ),
4707                );
4708                assert!(filename.is_some(), "Expected there to be a filename in error message");
4709            }
4710        );
4711
4712        let input = r##"{
4713            children: [
4714                {
4715                    name: "logger",
4716                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
4717                },
4718            ],
4719            collections: [
4720                {
4721                    name: "coll",
4722                    durability: "transient",
4723                },
4724            ],
4725            offer: [
4726                {
4727                    protocol: "fuchsia.logger.LogSink",
4728                    from: "parent",
4729                    to: "all"
4730                },
4731                {
4732                    protocol: "diagnostics",
4733                    from: "parent",
4734                    to: "all"
4735                },
4736                {
4737                    dictionary: "diagnostics",
4738                    from: "parent",
4739                    to: "#logger"
4740                },
4741            ]
4742        }"##;
4743        let result = validate_with_features_for_test(
4744            "test.cml",
4745            input.as_bytes(),
4746            &FeatureSet::empty(),
4747            &vec!["fuchsia.logger.LogSink".into()],
4748            &[],
4749            &["diagnostics".to_string()],
4750        );
4751        assert_matches!(result,
4752            Err(Error::Validate { err, filename }) => {
4753                assert_eq!(
4754                    err,
4755                    fail_to_make_required_offer_dictionary("diagnostics", "collection", "coll"),
4756                );
4757                assert!(filename.is_some(), "Expected there to be a filename in error message");
4758            }
4759        );
4760    }
4761
4762    #[test]
4763    fn fail_to_offer_dictionary_to_all_when_required_even_if_protocol_called_diagnostics_offered() {
4764        let input = r##"{
4765            children: [
4766                {
4767                    name: "logger",
4768                    url: "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
4769                },
4770                {
4771                    name: "something",
4772                    url: "fuchsia-pkg://fuchsia.com/something#meta/something.cm",
4773                },
4774            ],
4775            offer: [
4776                {
4777                    protocol: "fuchsia.logger.LogSink",
4778                    from: "parent",
4779                    to: "all"
4780                },
4781                {
4782                    protocol: "diagnostics",
4783                    from: "parent",
4784                    to: "all"
4785                },
4786                {
4787                    protocol: "fuchsia.logger.LegacyLog",
4788                    from: "parent",
4789                    to: "#something"
4790                },
4791            ]
4792        }"##;
4793        let result = validate_with_features_for_test(
4794            "test.cml",
4795            input.as_bytes(),
4796            &FeatureSet::empty(),
4797            &[],
4798            &[],
4799            &["diagnostics".to_string()],
4800        );
4801
4802        assert_matches!(result,
4803            Err(Error::Validate { err, filename }) => {
4804                assert_eq!(
4805                    err,
4806                    fail_to_make_required_offer_dictionary(
4807                        "diagnostics",
4808                        "child component",
4809                        "logger",
4810                    ),
4811                );
4812                assert!(filename.is_some(), "Expected there to be a filename in error message");
4813            }
4814        );
4815    }
4816
4817    #[test]
4818    fn test_validate_invalid_json_fails() {
4819        let result = validate_for_test("test.cml", b"{");
4820        let expected_err = r#" --> 1:2
4821  |
48221 | {
4823  |  ^---
4824  |
4825  = expected identifier or string"#;
4826        assert_matches!(result, Err(Error::Parse { err, .. }) if &err == expected_err);
4827    }
4828
4829    #[test]
4830    fn test_cml_json5() {
4831        let input = r##"{
4832            "expose": [
4833                // Here are some services to expose.
4834                { "protocol": "fuchsia.logger.Log", "from": "#logger", },
4835                { "directory": "blobfs", "from": "#logger", "rights": ["rw*"]},
4836            ],
4837            "children": [
4838                {
4839                    name: 'logger',
4840                    'url': 'fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm',
4841                },
4842            ],
4843        }"##;
4844        let result = validate_for_test("test.cml", input.as_bytes());
4845        assert_matches!(result, Ok(()));
4846    }
4847
4848    #[test]
4849    fn test_cml_error_location() {
4850        let input = r##"{
4851    "use": [
4852        {
4853            "protocol": "foo",
4854            "from": "bad",
4855        },
4856    ],
4857}"##;
4858        let result = validate_for_test("test.cml", input.as_bytes());
4859        assert_matches!(
4860            result,
4861            Err(Error::Parse { err, location: Some(l), filename: Some(f) })
4862                if &err == "invalid value: string \"bad\", expected \"parent\", \"framework\", \"debug\", \"self\", \"#<capability-name>\", \"#<child-name>\", \"#<collection-name>\", dictionary path, or none" &&
4863                l == Location { line: 5, column: 21 } &&
4864                f.ends_with("test.cml")
4865        );
4866    }
4867
4868    test_validate_cml_with_context! {
4869        test_cml_empty_json(
4870            json!({}),
4871            Ok(())
4872        ),
4873
4874        test_cml_children_url_ends_in_cml(
4875            r##"{
4876                "children": [
4877                    {
4878                        "name": "logger",
4879                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cml"
4880                    }
4881                ]
4882            }"##,
4883            Err(Error::ValidateContext { 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"),
4884
4885        test_cml_allow_long_names_without_feature(
4886            json!({
4887                "collections": [
4888                    {
4889                        "name": "foo",
4890                        "durability": "transient",
4891                        "allow_long_names": true
4892                    },
4893                ],
4894            }),
4895            Err(Error::RestrictedFeature(s)) if s == "allow_long_names"
4896        ),
4897
4898        test_cml_directory_missing_path(
4899            r##"{
4900                "capabilities": [
4901                    {
4902                        "directory": "dir",
4903                        "rights": ["connect"]
4904                    }
4905                ]
4906            }"##,
4907            Err(Error::ValidateContext { err, ..}) if &err == "\"path\" should be present with \"directory\""
4908        ),
4909        test_cml_directory_missing_rights(
4910            r##"{
4911                "capabilities": [
4912                    {
4913                        "directory": "dir",
4914                        "path": "/dir"
4915                    }
4916                ]
4917            }"##,
4918            Err(Error::ValidateContext { err, .. }) if &err == "\"rights\" should be present with \"directory\""),
4919
4920        test_cml_storage_missing_from(
4921                r##"{
4922                "capabilities": [
4923                    {
4924                        "storage": "data-storage",
4925                        "backing_dir": "minfs",
4926                        "storage_id": "static_instance_id_or_moniker"
4927                    }
4928                ]
4929            }"##,
4930            Err(Error::ValidateContext { err, .. }) if &err == "\"from\" should be present with \"storage\""),
4931
4932
4933        test_cml_storage_path(
4934            r##"{
4935                    "capabilities": [ {
4936                        "storage": "minfs",
4937                        "from": "self",
4938                        "path": "/minfs",
4939                        "storage_id": "static_instance_id_or_moniker"
4940                    } ]
4941                }"##,
4942            Err(Error::ValidateContext { err, .. }) if &err == "\"path\" cannot be present with \"storage\", use \"backing_dir\""),
4943
4944        test_cml_storage_missing_path_or_backing_dir(
4945            r##"{
4946                    "capabilities": [ {
4947                        "storage": "minfs",
4948                        "from": "self",
4949                        "storage_id": "static_instance_id_or_moniker"
4950                    } ]
4951                }"##,
4952            Err(Error::ValidateContext { err, .. }) if &err == "\"backing_dir\" should be present with \"storage\""
4953        ),
4954
4955        test_cml_storage_missing_storage_id(
4956            r##"{
4957                    "capabilities": [ {
4958                        "storage": "minfs",
4959                        "from": "self",
4960                        "backing_dir": "storage"
4961                    } ]
4962                }"##,
4963            Err(Error::ValidateContext{ err, .. }) if &err == "\"storage_id\" should be present with \"storage\""),
4964
4965        test_cml_capabilities_extraneous_resolver_from(
4966            r##"{
4967                "capabilities": [
4968                    {
4969                        "resolver": "pkg_resolver",
4970                        "path": "/svc/fuchsia.component.resolution.Resolver",
4971                        "from": "self"
4972                    }
4973                ]
4974            }"##,
4975            Err(Error::ValidateContext { err, .. }) if &err == "\"from\" should not be present with \"resolver\""),
4976
4977        test_cml_resolver_missing_path(
4978            r##"{
4979                "capabilities": [
4980                    {
4981                        "resolver": "pkg_resolver"
4982                    }
4983                ]
4984            }"##,
4985            Err(Error::ValidateContext { err, .. }) if &err == "\"path\" should be present with \"resolver\""),
4986
4987        test_cml_runner_missing_path(
4988            r##"{
4989                "capabilities": [
4990                    {
4991                        "runner": "runrun"
4992                    }
4993                ]
4994            }"##,
4995            Err(Error::ValidateContext { err, .. }) if &err == "\"path\" should be present with \"runner\""
4996        ),
4997
4998        test_cml_runner_extraneous_from(
4999            r##"{
5000                "capabilities": [
5001                    {
5002                        "runner": "a",
5003                        "path": "/example",
5004                        "from": "self"
5005                    }
5006                ]
5007            }"##,
5008            Err(Error::ValidateContext { err, .. }) if &err == "\"from\" should not be present with \"runner\""
5009        ),
5010
5011        test_cml_service_multi_invalid_path(
5012            r##"{
5013                "capabilities": [
5014                    {
5015                        "service": ["a", "b", "c"],
5016                        "path": "/minfs"
5017                    }
5018                ]
5019            }"##,
5020            Err(Error::ValidateContext{ err, .. }) if &err == "\"path\" can only be specified when one `service` is supplied."
5021        ),
5022
5023        test_cml_protocol_multi_invalid_path(
5024            r##"{
5025                "capabilities": [
5026                    {
5027                        "protocol": ["a", "b", "c"],
5028                        "path": "/minfs"
5029                    }
5030                ]
5031            }"##,
5032            Err(Error::ValidateContext { err, .. }) if &err == "\"path\" can only be specified when one `protocol` is supplied."),
5033
5034        test_cml_use_bad_duplicate_target_names(
5035            r##"{
5036                "use": [
5037                  { "protocol": "fuchsia.component.Realm" },
5038                  { "protocol": "fuchsia.component.Realm" }
5039                ]
5040            }"##,
5041            Err(Error::ValidateContexts { err, .. }) if &err == "\"/svc/fuchsia.component.Realm\" is a duplicate \"use\" target protocol"),
5042
5043        test_cml_use_disallows_nested_dirs_directory(
5044            r##"{
5045                "use": [
5046                    { "directory": "foobar", "path": "/foo/bar", "rights": [ "r*" ] },
5047                    { "directory": "foobarbaz", "path": "/foo/bar/baz", "rights": [ "r*" ] }
5048                ]
5049            }"##,
5050            Err(Error::ValidateContexts { err, .. }) if &err == "directory \"/foo/bar\" is a prefix of \"use\" target directory \"/foo/bar/baz\""
5051        ),
5052        test_cml_use_disallows_nested_dirs_storage(
5053            r##"{
5054                "use": [
5055                    { "storage": "foobar", "path": "/foo/bar" },
5056                    { "storage": "foobarbaz", "path": "/foo/bar/baz" }
5057                ]
5058            }"##,
5059            Err(Error::ValidateContexts { err, .. }) if &err == "storage \"/foo/bar\" is a prefix of \"use\" target storage \"/foo/bar/baz\""
5060        ),
5061        test_cml_use_disallows_nested_dirs_directory_and_storage(
5062            r##"{
5063                "use": [
5064                    { "directory": "foobar", "path": "/foo/bar", "rights": [ "r*" ] },
5065                    { "storage": "foobarbaz", "path": "/foo/bar/baz" }
5066                ]
5067            }"##,
5068            Err(Error::ValidateContexts { err, .. }) if &err == "directory \"/foo/bar\" is a prefix of \"use\" target storage \"/foo/bar/baz\""
5069        ),
5070         test_cml_use_disallows_common_prefixes_service(
5071             r##"{
5072                 "use": [
5073                     { "directory": "foobar", "path": "/foo/bar", "rights": [ "r*" ] },
5074                     { "protocol": "fuchsia", "path": "/foo/bar/fuchsia" }
5075                 ]
5076             }"##,
5077             Err(Error::ValidateContexts { err, .. }) if &err == "directory \"/foo/bar\" is a prefix of \"use\" target protocol \"/foo/bar/fuchsia\""
5078        ),
5079        test_cml_use_disallows_common_prefixes_protocol(
5080            r##"{
5081                "use": [
5082                    { "directory": "foobar", "path": "/foo/bar", "rights": [ "r*" ] },
5083                    { "protocol": "fuchsia", "path": "/foo/bar/fuchsia.2" }
5084                ]
5085            }"##,
5086            Err(Error::ValidateContexts { err, .. }) if &err == "directory \"/foo/bar\" is a prefix of \"use\" target protocol \"/foo/bar/fuchsia.2\""
5087        ),
5088
5089
5090        test_cml_use_invalid_from_with_service(
5091            json!({
5092                "use": [ { "service": "foo", "from": "debug" } ]
5093            }),
5094            Err(Error::ValidateContext { err, .. }) if &err == "only \"protocol\" supports source from \"debug\""
5095        ),
5096
5097        test_cml_use_runner_debug_ref(
5098            r##"{
5099                "use": [
5100                    {
5101                        "runner": "elf",
5102                        "from": "debug"
5103                    }
5104                ]
5105            }"##,
5106            Err(Error::ValidateContext { err, .. }) if &err == "only \"protocol\" supports source from \"debug\""
5107        ),
5108
5109        test_cml_availability_not_supported_for_event_streams(
5110            r##"{
5111                "use": [
5112                    {
5113                        "event_stream": ["destroyed"],
5114                        "from": "parent",
5115                        "availability": "optional"
5116                    }
5117                ]
5118            }"##,
5119            Err(Error::ValidateContext { err, .. }) if &err == "\"availability\" cannot be used with \"event_stream\""
5120        ),
5121
5122        test_cml_use_disallows_filter_on_non_events(
5123            json!({
5124                "use": [
5125                    { "directory": "foobar", "path": "/foo/bar", "rights": [ "r*" ], "filter": {"path": "/diagnostics"} },
5126                ],
5127            }),
5128            Err(Error::ValidateContext { err, .. }) if &err == "\"filter\" can only be used with \"event_stream\""
5129        ),
5130
5131        test_cml_use_from_with_storage(
5132            json!({
5133                "use": [ { "storage": "cache", "from": "parent" } ]
5134            }),
5135            Err(Error::ValidateContext { err, .. }) if &err == "\"from\" cannot be used with \"storage\""
5136        ),
5137
5138        test_cml_availability_not_supported_for_runner(
5139            r##"{
5140                "use": [
5141                    {
5142                        "runner": "destroyed",
5143                        "from": "parent",
5144                        "availability": "optional"
5145                    }
5146                ]
5147            }"##,
5148            Err(Error::ValidateContext { err, .. }) if &err == "\"availability\" cannot be used with \"runner\""
5149        ),
5150
5151        test_cml_use_event_stream_self_ref(
5152            r##"{
5153                "use": [
5154                    {
5155                        "event_stream": ["started"],
5156                        "path": "/svc/my_stream",
5157                        "from": "self"
5158                    }
5159                ]
5160            }"##,
5161            Err(Error::ValidateContext { err, .. }) if &err == "\"from: self\" cannot be used with \"event_stream\""
5162        ),
5163
5164        test_cml_use_runner_self_ref(
5165            r##"{
5166                "use": [
5167                    {
5168                        "runner": "elf",
5169                        "from": "self"
5170                    }
5171                ]
5172            }"##,
5173            Err(Error::ValidateContext { err, .. }) if &err == "\"from: self\" cannot be used with \"runner\""
5174        ),
5175
5176        test_cml_use_invalid_availability(
5177            r##"{
5178                "use": [
5179                    {
5180                        "protocol": "fuchsia.examples.Echo",
5181                        "availability": "same_as_target"
5182                    }
5183                ]
5184            }"##,
5185            Err(Error::ValidateContext { err, .. }) if &err == "\"availability: same_as_target\" cannot be used with use declarations"
5186        ),
5187
5188        test_cml_use_config_bad_string(
5189            r##"{
5190                "use": [
5191                    {
5192                        "config": "fuchsia.config.MyConfig",
5193                        "key": "my_config",
5194                        "type": "string"
5195                    }
5196                ]
5197            }"##,
5198            Err(Error::ValidateContext { err, .. })
5199            if &err == "Config 'fuchsia.config.MyConfig' is type String but is missing field 'max_size'"
5200        ),
5201
5202        test_config_required_with_default(
5203            r##"{"use": [
5204                {
5205                    "config": "fuchsia.config.MyConfig",
5206                    "key": "my_config",
5207                    "type": "bool",
5208                    "default": "true"
5209                }
5210            ]}"##,
5211            Err(Error::ValidateContext {err, ..})
5212            if &err == "Config 'fuchsia.config.MyConfig' is required and has a default value"
5213        ),
5214
5215        test_cml_use_numbered_handle_and_path(
5216            json!({
5217                "use": [
5218                    {
5219                        "protocol": "foo",
5220                        "path": "/svc/foo",
5221                        "numbered_handle": 0xab
5222                    }
5223                ]
5224        }),
5225            Err(Error::ValidateContext { err, .. }) if &err == "`path` and `numbered_handle` are incompatible"
5226        ),
5227
5228        test_cml_use_numbered_handle_not_protocol(
5229            json!({
5230                "use": [
5231                    {
5232                        "runner": "foo",
5233                        "numbered_handle": 0xab
5234                    }
5235                ]
5236            }),
5237            Err(Error::ValidateContext { err, .. }) if &err == "`numbered_handle` is only supported for `use protocol`"
5238        ),
5239
5240        test_cml_expose_invalid_subdir_to_framework(
5241            r##"{
5242                "capabilities": [
5243                    {
5244                        "directory": "foo",
5245                        "rights": ["r*"],
5246                        "path": "/foo"
5247                    }
5248                ],
5249                "expose": [
5250                    {
5251                        "directory": "foo",
5252                        "from": "self",
5253                        "to": "framework",
5254                        "subdir": "blob"
5255                    }
5256                ],
5257                "children": [
5258                    {
5259                        "name": "child",
5260                        "url": "fuchsia-pkg://fuchsia.com/pkg#comp.cm"
5261                    }
5262                ]
5263            }"##,
5264            Err(Error::ValidateContext { err, .. }) if &err == "`subdir` is not supported for expose to framework. Directly expose the subdirectory instead."
5265        ),
5266        test_cml_expose_event_stream_multiple_as(
5267            r##"{
5268                "expose": [
5269                    {
5270                        "event_stream": ["started", "stopped"],
5271                        "from" : "framework",
5272                        "as": "something"
5273                    }
5274                ]
5275            }"##,
5276            Err(Error::ValidateContext { err, .. }) if &err == "as cannot be used with multiple event streams"
5277        ),
5278
5279        test_cml_expose_event_stream_to_framework(
5280            r##"{
5281                "expose": [
5282                    {
5283                        "event_stream": ["started", "stopped"],
5284                        "from" : "self",
5285                        "to": "framework"
5286                    }
5287                ]
5288            }"##,
5289            Err(Error::ValidateContext { err, .. }) if &err == "cannot expose an event_stream to framework"
5290        ),
5291
5292        test_cml_expose_event_stream_from_self(
5293            json!({
5294                "expose": [
5295                    { "event_stream": ["started", "stopped"], "from" : "self" },
5296                ]
5297            }),
5298            Err(Error::ValidateContext { err, .. }) if &err == "Cannot expose event_streams from self"
5299        ),
5300
5301        test_cml_offer_event_stream_from_self(
5302            json!({
5303                "offer": [
5304                    { "event_stream": ["started", "stopped"], "from" : "self", "to": "#self" },
5305                ]
5306            }),
5307            Err(Error::ValidateContext { err, .. }) if &err == "cannot offer an event_stream from self"
5308        ),
5309
5310        test_cml_rights_alias_star_expansion_collision(
5311            r##"{
5312                "use": [
5313                  {
5314                    "directory": "mydir",
5315                    "path": "/mydir",
5316                    "rights": ["w*", "x*"]
5317                  }
5318                ]
5319            }"##,
5320            Err(Error::ValidateContext { err, ..  }) if &err == "\"x*\" is duplicated in the rights clause."
5321        ),
5322
5323        test_cml_rights_alias_star_expansion_with_longform_collision(
5324            r##"{
5325                "use": [
5326                  {
5327                    "directory": "mydir",
5328                    "path": "/mydir",
5329                    "rights": ["r*", "read_bytes"]
5330                  }
5331                ]
5332            }"##,
5333            Err(Error::ValidateContext { err, ..}) if &err == "\"read_bytes\" is duplicated in the rights clause."
5334        ),
5335
5336        test_cml_rights_use_invalid(
5337            json!({
5338                "use": [
5339                  { "directory": "mydir", "path": "/mydir" },
5340                ]
5341            }),
5342            Err(Error::ValidateContexts { err, .. }) if &err == "This use statement requires a `rights` field. Refer to: https://fuchsia.dev/go/components/directory#consumer."
5343        ),
5344        test_cml_use_missing_props(
5345            json!({
5346                "use": [ { "path": "/svc/fuchsia.logger.Log" } ]
5347            }),
5348            Err(Error::ValidateContext { err, .. }) if &err == "`use` declaration is missing a capability keyword, one of: \"service\", \"protocol\", \"directory\", \"storage\", \"event_stream\", \"runner\", \"config\", \"dictionary\""
5349        ),
5350
5351
5352        test_cml_use_two_types_bad(
5353            r##"{"use": [
5354                {
5355                    "protocol": "fuchsia.protocol.MyProtocol",
5356                    "service": "fuchsia.service.MyService"
5357                }
5358            ]
5359        }"##,
5360            Err(Error::ValidateContext {err, ..})
5361            if &err == "use declaration has multiple capability types defined: [\"service\", \"protocol\"]"
5362            ),
5363    test_cml_expose_two_types_bad(
5364        r##"{"expose": [
5365            {
5366                "protocol": "fuchsia.protocol.MyProtocol",
5367                "service": "fuchsia.service.MyService",
5368                "from" : "self"
5369            }
5370        ]
5371    }"##,
5372        Err(Error::ValidateContext {err, ..})
5373        if &err == "expose declaration has multiple capability types defined: [\"service\", \"protocol\"]"
5374        ),
5375
5376        test_cml_expose_from_self(
5377            json!({
5378                "expose": [
5379                    {
5380                        "protocol": "foo_protocol",
5381                        "from": "self",
5382                    },
5383                    {
5384                        "protocol": [ "bar_protocol", "baz_protocol" ],
5385                        "from": "self",
5386                    },
5387                    {
5388                        "directory": "foo_directory",
5389                        "from": "self",
5390                    },
5391                    {
5392                        "runner": "foo_runner",
5393                        "from": "self",
5394                    },
5395                    {
5396                        "resolver": "foo_resolver",
5397                        "from": "self",
5398                    },
5399                ],
5400                "capabilities": [
5401                    {
5402                        "protocol": "foo_protocol",
5403                    },
5404                    {
5405                        "protocol": "bar_protocol",
5406                    },
5407                    {
5408                        "protocol": "baz_protocol",
5409                    },
5410                    {
5411                        "directory": "foo_directory",
5412                        "path": "/dir",
5413                        "rights": [ "r*" ],
5414                    },
5415                    {
5416                        "runner": "foo_runner",
5417                        "path": "/svc/runner",
5418                    },
5419                    {
5420                        "resolver": "foo_resolver",
5421                        "path": "/svc/resolver",
5422                    },
5423                ]
5424            }),
5425            Ok(())
5426        ),
5427        test_cml_expose_protocol_from_self_missing(
5428            json!({
5429                "expose": [
5430                    {
5431                        "protocol": "pkg_protocol",
5432                        "from": "self",
5433                    },
5434                ],
5435            }),
5436            Err(Error::ValidateContext { err, .. }) if &err == "protocol \"pkg_protocol\" is exposed from self, so it must be declared as a \"protocol\" in \"capabilities\""
5437        ),
5438        test_cml_expose_protocol_from_self_missing_multiple(
5439            json!({
5440                "expose": [
5441                    {
5442                        "protocol": [ "foo_protocol", "bar_protocol" ],
5443                        "from": "self",
5444                    },
5445                ],
5446            }),
5447            Err(Error::ValidateContext { err, .. }) if &err == "protocol \"foo_protocol\" is exposed from self, so it must be declared as a \"protocol\" in \"capabilities\""
5448        ),
5449        test_cml_expose_directory_from_self_missing(
5450            json!({
5451                "expose": [
5452                    {
5453                        "directory": "pkg_directory",
5454                        "from": "self",
5455                    },
5456                ],
5457            }),
5458            Err(Error::ValidateContext { err, .. }) if &err == "directory \"pkg_directory\" is exposed from self, so it must be declared as a \"directory\" in \"capabilities\""
5459        ),
5460        test_cml_expose_service_from_self_missing(
5461            json!({
5462                "expose": [
5463                    {
5464                        "service": "pkg_service",
5465                        "from": "self",
5466                    },
5467                ],
5468            }),
5469            Err(Error::ValidateContext { err, .. }) if &err == "service \"pkg_service\" is exposed from self, so it must be declared as a \"service\" in \"capabilities\""
5470        ),
5471        test_cml_expose_runner_from_self_missing(
5472            json!({
5473                "expose": [
5474                    {
5475                        "runner": "dart",
5476                        "from": "self",
5477                    },
5478                ],
5479            }),
5480            Err(Error::ValidateContext { err, .. }) if &err == "runner \"dart\" is exposed from self, so it must be declared as a \"runner\" in \"capabilities\""
5481        ),
5482        test_cml_expose_resolver_from_self_missing(
5483            json!({
5484                "expose": [
5485                    {
5486                        "resolver": "pkg_resolver",
5487                        "from": "self",
5488                    },
5489                ],
5490            }),
5491            Err(Error::ValidateContext { err, .. }) if &err == "resolver \"pkg_resolver\" is exposed from self, so it must be declared as a \"resolver\" in \"capabilities\""
5492        ),
5493        test_cml_expose_from_self_missing_dictionary(
5494            json!({
5495                "expose": [
5496                    {
5497                        "protocol": "foo_protocol",
5498                        "from": "self/dict/inner",
5499                    },
5500                ],
5501            }),
5502            Err(Error::ValidateContext { err, .. }) if &err == "protocol \"foo_protocol\" is exposed from \"self/dict/inner\", so \"dict\" must be declared as a \"dictionary\" in \"capabilities\""
5503        ),
5504
5505                test_cml_expose_from_dictionary_parent(
5506            json!({
5507                "expose": [
5508                    {
5509                        "protocol": "pkg_protocol",
5510                        "from": "parent/a",
5511                    },
5512                ],
5513            }),
5514            Err(Error::ValidateContext { err, .. }) if &err == "`expose` dictionary path must begin with `self` or `#<child-name>`"
5515        ),
5516        test_cml_expose_protocol_from_collection_invalid(
5517            json!({
5518                "collections": [ {
5519                    "name": "coll",
5520                    "durability": "transient",
5521                } ],
5522                "expose": [
5523                    { "protocol": "fuchsia.logger.Log", "from": "#coll" },
5524                ]
5525            }),
5526            Err(Error::ValidateContext { err, .. }) if &err == "\"expose\" source \"#coll\" does not appear in \"children\" or \"capabilities\""
5527        ),
5528        test_cml_expose_directory_from_collection_invalid(
5529            json!({
5530                "collections": [ {
5531                    "name": "coll",
5532                    "durability": "transient",
5533                } ],
5534                "expose": [
5535                    { "directory": "temp", "from": "#coll" },
5536                ]
5537            }),
5538            Err(Error::ValidateContext { err, .. }) if &err == "\"expose\" source \"#coll\" does not appear in \"children\""
5539        ),
5540        test_cml_expose_runner_from_collection_invalid(
5541            json!({
5542                "collections": [ {
5543                    "name": "coll",
5544                    "durability": "transient",
5545                } ],
5546                "expose": [
5547                    { "runner": "elf", "from": "#coll" },
5548                ]
5549            }),
5550            Err(Error::ValidateContext { err, .. }) if &err == "\"expose\" source \"#coll\" does not appear in \"children\""
5551        ),
5552        test_cml_expose_resolver_from_collection_invalid(
5553            json!({
5554                "collections": [ {
5555                    "name": "coll",
5556                    "durability": "transient",
5557                } ],
5558                "expose": [
5559                    { "resolver": "base", "from": "#coll" },
5560                ]
5561            }),
5562            Err(Error::ValidateContext { err, .. }) if &err == "\"expose\" source \"#coll\" does not appear in \"children\""
5563        ),
5564
5565        test_cml_offer_from_self(
5566            json!({
5567                "offer": [
5568                    {
5569                        "protocol": "foo_protocol",
5570                        "from": "self",
5571                        "to": [ "#modular" ],
5572                    },
5573                    {
5574                        "protocol": [ "bar_protocol", "baz_protocol" ],
5575                        "from": "self",
5576                        "to": [ "#modular" ],
5577                    },
5578                    {
5579                        "directory": "foo_directory",
5580                        "from": "self",
5581                        "to": [ "#modular" ],
5582                    },
5583                    {
5584                        "runner": "foo_runner",
5585                        "from": "self",
5586                        "to": [ "#modular" ],
5587                    },
5588                    {
5589                        "resolver": "foo_resolver",
5590                        "from": "self",
5591                        "to": [ "#modular" ],
5592                    },
5593                ],
5594                "children": [
5595                    {
5596                        "name": "modular",
5597                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
5598                    },
5599                ],
5600                "capabilities": [
5601                    {
5602                        "protocol": "foo_protocol",
5603                    },
5604                    {
5605                        "protocol": "bar_protocol",
5606                    },
5607                    {
5608                        "protocol": "baz_protocol",
5609                    },
5610                    {
5611                        "directory": "foo_directory",
5612                        "path": "/dir",
5613                        "rights": [ "r*" ],
5614                    },
5615                    {
5616                        "runner": "foo_runner",
5617                        "path": "/svc/fuchsia.sys2.ComponentRunner",
5618                    },
5619                    {
5620                        "resolver": "foo_resolver",
5621                        "path": "/svc/fuchsia.component.resolution.Resolver",
5622                    },
5623                ]
5624            }),
5625            Ok(())
5626        ),
5627        test_cml_offer_service_from_self_missing(
5628            json!({
5629                "offer": [
5630                    {
5631                        "service": "pkg_service",
5632                        "from": "self",
5633                        "to": [ "#modular" ],
5634                    },
5635                ],
5636                "children": [
5637                    {
5638                        "name": "modular",
5639                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
5640                    },
5641                ],
5642            }),
5643            Err(Error::ValidateContext { err, .. }) if &err == "service \"pkg_service\" is offered from self, so it must be declared as a \"service\" in \"capabilities\""
5644        ),
5645        test_cml_offer_protocol_from_self_missing(
5646            json!({
5647                "offer": [
5648                    {
5649                        "protocol": "pkg_protocol",
5650                        "from": "self",
5651                        "to": [ "#modular" ],
5652                    },
5653                ],
5654                "children": [
5655                    {
5656                        "name": "modular",
5657                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
5658                    },
5659                ],
5660            }),
5661            Err(Error::ValidateContext { err, .. }) if &err == "protocol \"pkg_protocol\" is offered from self, so it must be declared as a \"protocol\" in \"capabilities\""
5662        ),
5663        test_cml_offer_protocol_from_self_missing_multiple(
5664            json!({
5665                "offer": [
5666                    {
5667                        "protocol": [ "foo_protocol", "bar_protocol" ],
5668                        "from": "self",
5669                        "to": [ "#modular" ],
5670                    },
5671                ],
5672                "children": [
5673                    {
5674                        "name": "modular",
5675                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
5676                    },
5677                ],
5678            }),
5679            Err(Error::ValidateContext { err, .. }) if &err == "protocol \"foo_protocol\" is offered from self, so it must be declared as a \"protocol\" in \"capabilities\""
5680        ),
5681        test_cml_offer_directory_from_self_missing(
5682            json!({
5683                "offer": [
5684                    {
5685                        "directory": "pkg_directory",
5686                        "from": "self",
5687                        "to": [ "#modular" ],
5688                    },
5689                ],
5690                "children": [
5691                    {
5692                        "name": "modular",
5693                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
5694                    },
5695                ],
5696            }),
5697            Err(Error::ValidateContext { err, .. }) if &err == "directory \"pkg_directory\" is offered from self, so it must be declared as a \"directory\" in \"capabilities\""
5698        ),
5699        test_cml_offer_runner_from_self_missing(
5700            json!({
5701                "offer": [
5702                    {
5703                        "runner": "dart",
5704                        "from": "self",
5705                        "to": [ "#modular" ],
5706                    },
5707                ],
5708                "children": [
5709                    {
5710                        "name": "modular",
5711                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
5712                    },
5713                ],
5714            }),
5715            Err(Error::ValidateContext { err, .. }) if &err == "runner \"dart\" is offered from self, so it must be declared as a \"runner\" in \"capabilities\""
5716        ),
5717        test_cml_offer_resolver_from_self_missing(
5718            json!({
5719                "offer": [
5720                    {
5721                        "resolver": "pkg_resolver",
5722                        "from": "self",
5723                        "to": [ "#modular" ],
5724                    },
5725                ],
5726                "children": [
5727                    {
5728                        "name": "modular",
5729                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
5730                    },
5731                ],
5732            }),
5733            Err(Error::ValidateContext { err, .. }) if &err == "resolver \"pkg_resolver\" is offered from self, so it must be declared as a \"resolver\" in \"capabilities\""
5734        ),
5735        test_cml_offer_storage_from_self_missing(
5736            json!({
5737                    "offer": [
5738                        {
5739                            "storage": "cache",
5740                            "from": "self",
5741                            "to": [ "#echo_server" ],
5742                        },
5743                    ],
5744                    "children": [
5745                        {
5746                            "name": "echo_server",
5747                            "url": "fuchsia-pkg://fuchsia.com/echo_server#meta/echo_server.cm",
5748                        },
5749                    ],
5750                }),
5751            Err(Error::ValidateContext { err, .. }) if &err == "storage \"cache\" is offered from self, so it must be declared as a \"storage\" in \"capabilities\""
5752        ),
5753        test_cml_offer_from_self_missing_dictionary(
5754            json!({
5755                "offer": [
5756                    {
5757                        "protocol": "foo_protocol",
5758                        "from": "self/dict/inner",
5759                        "to": [ "#modular" ],
5760                    },
5761                ],
5762                "children": [
5763                    {
5764                        "name": "modular",
5765                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
5766                    },
5767                ],
5768            }),
5769            Err(Error::ValidateContext { err, .. }) if &err == "protocol \"foo_protocol\" is offered from \"self/dict/inner\", so \"dict\" must be declared as a \"dictionary\" in \"capabilities\""
5770        ),
5771
5772        test_cml_storage_offer_from_child(
5773            r##"{
5774                    "offer": [
5775                        {
5776                            "storage": "cache",
5777                            "from": "#storage_provider",
5778                            "to": [ "#echo_server" ]
5779                        }
5780                    ],
5781                    "children": [
5782                        {
5783                            "name": "echo_server",
5784                            "url": "fuchsia-pkg://fuchsia.com/echo_server#meta/echo_server.cm"
5785                        },
5786                        {
5787                            "name": "storage_provider",
5788                            "url": "fuchsia-pkg://fuchsia.com/storage_provider#meta/storage_provider.cm"
5789                        }
5790                    ]
5791                }"##,
5792            Err(Error::ValidateContexts { err, .. }) if &err == "Storage \"cache\" is offered from a child, but storage capabilities cannot be exposed"
5793        ),
5794
5795        test_cml_offer_storage_from_collection_invalid(
5796            r##"{
5797                "collections": [ {
5798                    "name": "coll",
5799                    "durability": "transient"
5800                } ],
5801                "children": [ {
5802                    "name": "echo_server",
5803                    "url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo_server.cm"
5804                } ],
5805                "offer": [
5806                    { "storage": "cache", "from": "#coll", "to": [ "#echo_server" ] }
5807                ]
5808            }"##,
5809            Err(Error::ValidateContexts { err, .. }) if &err == "Storage \"cache\" is offered from a child, but storage capabilities cannot be exposed"
5810        ),
5811
5812        test_cml_children_bad_environment(
5813            json!({
5814                "children": [
5815                    {
5816                        "name": "logger",
5817                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
5818                        "environment": "parent",
5819                    }
5820                ]
5821            }),
5822            Err(Error::Parse { err, .. }) if err.starts_with("invalid value: string \"parent\", expected \"#<environment-name>\"")
5823        ),
5824        test_cml_children_environment(
5825            json!({
5826                "children": [
5827                    {
5828                        "name": "logger",
5829                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
5830                        "environment": "#foo_env",
5831                    }
5832                ],
5833                "environments": [
5834                    {
5835                        "name": "foo_env",
5836                    }
5837                ]
5838            }),
5839            Ok(())
5840        ),
5841        test_cml_collections_bad_environment(
5842            json!({
5843                "collections": [
5844                    {
5845                        "name": "tests",
5846                        "durability": "transient",
5847                        "environment": "parent",
5848                    }
5849                ]
5850            }),
5851            Err(Error::Parse { err, .. }) if err.starts_with("invalid value: string \"parent\", expected \"#<environment-name>\"")
5852        ),
5853        test_cml_collections_environment(
5854            json!({
5855                "collections": [
5856                    {
5857                        "name": "tests",
5858                        "durability": "transient",
5859                        "environment": "#foo_env",
5860                    }
5861                ],
5862                "environments": [
5863                    {
5864                        "name": "foo_env",
5865                    }
5866                ]
5867            }),
5868            Ok(())
5869        ),
5870
5871        test_cml_environment_timeout(
5872            json!({
5873                "environments": [
5874                    {
5875                        "name": "foo_env",
5876                        "__stop_timeout_ms": 10000,
5877                    }
5878                ]
5879            }),
5880            Ok(())
5881        ),
5882        test_cml_environment_bad_timeout(
5883            json!({
5884                "environments": [
5885                    {
5886                        "name": "foo_env",
5887                        "__stop_timeout_ms": -3,
5888                    }
5889                ]
5890            }),
5891            Err(Error::Parse { err, .. }) if err.starts_with("invalid value: integer `-3`, expected an unsigned 32-bit integer")
5892        ),
5893
5894        test_cml_environment_debug(
5895            json!({
5896                "capabilities": [
5897                    {
5898                        "protocol": "fuchsia.logger.Log2",
5899                    },
5900                ],
5901                "environments": [
5902                    {
5903                        "name": "foo_env",
5904                        "extends": "realm",
5905                        "debug": [
5906                            {
5907                                "protocol": "fuchsia.module.Module",
5908                                "from": "#modular",
5909                            },
5910                            {
5911                                "protocol": "fuchsia.logger.OtherLog",
5912                                "from": "parent",
5913                            },
5914                            {
5915                                "protocol": "fuchsia.logger.Log2",
5916                                "from": "self",
5917                            },
5918                        ]
5919                    }
5920                ],
5921                "children": [
5922                    {
5923                        "name": "modular",
5924                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
5925                    },
5926                ],
5927            }),
5928           Ok(())
5929        ),
5930
5931        test_cml_environment_debug_missing_capability(
5932            json!({
5933                "environments": [
5934                    {
5935                        "name": "foo_env",
5936                        "extends": "realm",
5937                        "debug": [
5938                            {
5939                                "protocol": "fuchsia.module.Module",
5940                                "from": "#modular",
5941                            },
5942                            {
5943                                "protocol": "fuchsia.logger.OtherLog",
5944                                "from": "parent",
5945                            },
5946                            {
5947                                "protocol": "fuchsia.logger.Log2",
5948                                "from": "self",
5949                            },
5950                        ]
5951                    }
5952                ],
5953                "children": [
5954                    {
5955                        "name": "modular",
5956                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
5957                    },
5958                ],
5959            }),
5960            Err(Error::ValidateContext { err, .. }) if &err == "protocol \"fuchsia.logger.Log2\" is registered as debug from self, so it must be declared as a \"protocol\" in \"capabilities\""
5961        ),
5962
5963        test_cml_environment_invalid_from_child(
5964            json!({
5965                "capabilities": [
5966                    {
5967                        "protocol": "fuchsia.logger.Log2",
5968                    },
5969                ],
5970                "environments": [
5971                    {
5972                        "name": "foo_env",
5973                        "extends": "realm",
5974                        "debug": [
5975                            {
5976                                "protocol": "fuchsia.module.Module",
5977                                "from": "#missing",
5978                            },
5979                            {
5980                                "protocol": "fuchsia.logger.OtherLog",
5981                                "from": "parent",
5982                            },
5983                            {
5984                                "protocol": "fuchsia.logger.Log2",
5985                                "from": "self",
5986                            },
5987                        ]
5988                    }
5989                ],
5990                "children": [
5991                    {
5992                        "name": "modular",
5993                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
5994                    },
5995                ],
5996            }),
5997            Err(Error::ValidateContext { err, .. }) if &err == "\"debug\" source \"#missing\" does not appear in \"children\" or \"capabilities\""
5998        ),
5999
6000        test_cml_facets(
6001            json!({
6002                "facets": {
6003                    "metadata": {
6004                        "title": "foo",
6005                        "authors": [ "me", "you" ],
6006                        "year": 2018
6007                    }
6008                }
6009            }),
6010            Ok(())
6011        ),
6012        test_cml_facets_wrong_type(
6013            json!({
6014                "facets": 55
6015            }),
6016            Err(Error::Parse { err, .. }) if err.starts_with("invalid type: integer `55`, expected a map")
6017        ),
6018
6019        test_cml_use_from_self(
6020            json!({
6021                "use": [
6022                    {
6023                        "protocol": [ "bar_protocol", "baz_protocol" ],
6024                        "from": "self",
6025                    },
6026                    {
6027                        "directory": "foo_directory",
6028                        "from": "self",
6029                        "path": "/dir",
6030                        "rights": [ "r*" ],
6031                    },
6032                    {
6033                        "service": "foo_service",
6034                        "from": "self",
6035                    },
6036                    {
6037                        "config": "foo_config",
6038                        "type": "bool",
6039                        "key": "k",
6040                        "from": "self",
6041                    },
6042                ],
6043                "capabilities": [
6044                    {
6045                        "protocol": "bar_protocol",
6046                    },
6047                    {
6048                        "protocol": "baz_protocol",
6049                    },
6050                    {
6051                        "directory": "foo_directory",
6052                        "path": "/dir",
6053                        "rights": [ "r*" ],
6054                    },
6055                    {
6056                        "service": "foo_service",
6057                    },
6058                    {
6059                        "config": "foo_config",
6060                        "type": "bool",
6061                    },
6062                ]
6063            }),
6064            Ok(())
6065        ),
6066        test_cml_use_protocol_from_self_missing(
6067            json!({
6068                "use": [
6069                    {
6070                        "protocol": "foo_protocol",
6071                        "from": "self",
6072                    },
6073                ],
6074            }),
6075            Err(Error::ValidateContext { err, .. }) if &err == "protocol \"foo_protocol\" is used from self, so it must be declared as a \"protocol\" in \"capabilities\""
6076        ),
6077
6078        test_cml_use_directory_from_self_missing(
6079            json!({
6080                "use": [
6081                    {
6082                        "directory": "foo_directory",
6083                        "from": "self",
6084                    },
6085                ],
6086            }),
6087            Err(Error::ValidateContext { err, .. }) if &err == "directory \"foo_directory\" is used from self, so it must be declared as a \"directory\" in \"capabilities\""
6088        ),
6089        test_cml_use_service_from_self_missing(
6090            json!({
6091                "use": [
6092                    {
6093                        "service": "foo_service",
6094                        "from": "self",
6095                    },
6096                ],
6097            }),
6098            Err(Error::ValidateContext { err, .. }) if &err == "service \"foo_service\" is used from self, so it must be declared as a \"service\" in \"capabilities\""
6099        ),
6100        test_cml_use_config_from_self_missing(
6101            json!({
6102                "use": [
6103                    {
6104                        "config": "foo_config",
6105                        "from": "self",
6106                    },
6107                ],
6108            }),
6109            Err(Error::ValidateContext { err, .. }) if &err == "config \"foo_config\" is used from self, so it must be declared as a \"config\" in \"capabilities\""
6110        ),
6111        test_cml_use_from_self_missing_dictionary(
6112            json!({
6113                "use": [
6114                    {
6115                        "protocol": "foo_protocol",
6116                        "from": "self/dict/inner",
6117                    },
6118                ],
6119            }),
6120            Err(Error::ValidateContext { err, .. }) if &err == "protocol \"foo_protocol\" is used from \"self/dict/inner\", so \"dict\" must be declared as a \"dictionary\" in \"capabilities\""
6121        ),
6122        test_cml_use_event_stream_duplicate(
6123            json!({
6124                "use": [
6125                    { "event_stream": ["started", "started"], "from" : "parent" },
6126                ]
6127            }),
6128            Err(Error::Parse { err, .. }) if err.starts_with("invalid value: array with duplicate element, expected a name or nonempty array of names, with unique elements")
6129        ),
6130        test_cml_use_event_stream_overlapping_path(
6131            json!({
6132                "use": [
6133                    { "directory": "foobarbaz", "path": "/foo/bar/baz", "rights": [ "r*" ] },
6134                    {
6135                        "event_stream": ["started"],
6136                        "path": "/foo/bar/baz/er",
6137                        "from": "parent",
6138                    },
6139                ],
6140            }),
6141            Err(Error::ValidateContexts { err, .. }) if &err == "directory \"/foo/bar/baz\" is a prefix of \"use\" target event_stream \"/foo/bar/baz/er\""
6142        ),
6143        test_cml_use_event_stream_invalid_path(
6144            json!({
6145                "use": [
6146                    {
6147                        "event_stream": ["started"],
6148                        "path": "my_stream",
6149                        "from": "parent",
6150                    },
6151                ],
6152            }),
6153            Err(Error::Parse { err, .. }) if err.starts_with("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")
6154        ),
6155
6156        test_cml_offer_bad_subdir(
6157            json!({
6158                "offer": [
6159                    {
6160                        "directory": "index",
6161                        "subdir": "/",
6162                        "from": "parent",
6163                        "to": [ "#modular" ],
6164                    },
6165                ],
6166                "children": [
6167                    {
6168                        "name": "modular",
6169                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
6170                    }
6171                ]
6172            }),
6173            Err(Error::Parse { err, .. }) if err.starts_with("invalid value: string \"/\", expected a path with no leading `/` and non-empty segments")
6174        ),
6175
6176    }
6177
6178    test_validate_cml! {
6179        // include
6180        test_cml_empty_include(
6181            json!(
6182                {
6183                    "include": [],
6184                }
6185            ),
6186            Ok(())
6187        ),
6188        test_cml_some_include(
6189            json!(
6190                {
6191                    "include": [ "some.cml" ],
6192                }
6193            ),
6194            Ok(())
6195        ),
6196        test_cml_couple_of_include(
6197            json!(
6198                {
6199                    "include": [ "some1.cml", "some2.cml" ],
6200                }
6201            ),
6202            Ok(())
6203        ),
6204
6205        // program
6206        test_cml_empty_json_no_span(
6207            json!({}),
6208            Ok(())
6209        ),
6210        test_cml_program(
6211            json!(
6212                {
6213                    "program": {
6214                        "runner": "elf",
6215                        "binary": "bin/app",
6216                    },
6217                }
6218            ),
6219            Ok(())
6220        ),
6221        test_cml_program_use_runner(
6222            json!(
6223                {
6224                    "program": {
6225                        "binary": "bin/app",
6226                    },
6227                    "use": [
6228                        { "runner": "elf", "from": "parent" }
6229                    ]
6230                }
6231            ),
6232            Ok(())
6233        ),
6234        test_cml_program_use_runner_conflict(
6235            json!(
6236                {
6237                    "program": {
6238                        "runner": "elf",
6239                        "binary": "bin/app",
6240                    },
6241                    "use": [
6242                        { "runner": "elf", "from": "parent" }
6243                    ]
6244                }
6245            ),
6246            Err(Error::Validate { err, .. }) if &err ==
6247                "Component has conflicting runners in `program` block and `use` block."
6248        ),
6249        test_cml_program_no_runner(
6250            json!({"program": { "binary": "bin/app" }}),
6251            Err(Error::Validate { err, .. }) if &err ==
6252                "Component has a `program` block defined, but doesn't specify a `runner`. \
6253                Components need to use a runner to actually execute code."
6254        ),
6255
6256        // use
6257        test_cml_use(
6258            json!({
6259                "use": [
6260                    { "protocol": "CoolFonts", "path": "/svc/MyFonts" },
6261                    { "protocol": "CoolFonts2", "path": "/svc/MyFonts2", "from": "parent/dict" },
6262                    { "protocol": "fuchsia.test.hub.HubReport", "from": "framework" },
6263                    { "protocol": "fuchsia.sys2.StorageAdmin", "from": "#data-storage" },
6264                    { "protocol": ["fuchsia.ui.scenic.Scenic", "fuchsia.logger.LogSink"] },
6265                    {
6266                        "directory": "assets",
6267                        "path": "/data/assets",
6268                        "rights": ["rw*"],
6269                    },
6270                    {
6271                        "directory": "config",
6272                        "from": "parent",
6273                        "path": "/data/config",
6274                        "rights": ["rx*"],
6275                        "subdir": "fonts/all",
6276                    },
6277                    { "storage": "data", "path": "/example" },
6278                    { "storage": "cache", "path": "/tmp" },
6279                    {
6280                        "event_stream": ["started", "stopped", "running"],
6281                        "scope":["#test"],
6282                        "path":"/svc/testpath",
6283                        "from":"parent",
6284                    },
6285                    { "runner": "usain", "from": "parent" },
6286                ],
6287                "capabilities": [
6288                    {
6289                        "storage": "data-storage",
6290                        "from": "parent",
6291                        "backing_dir": "minfs",
6292                        "storage_id": "static_instance_id_or_moniker",
6293                    }
6294                ]
6295            }),
6296            Ok(())
6297        ),
6298        test_cml_expose_event_stream_multiple_as_no_span(
6299            json!({
6300                "expose": [
6301                    {
6302                        "event_stream": ["started", "stopped"],
6303                        "from" : "framework",
6304                        "as": "something"
6305                    },
6306                ]
6307            }),
6308            Err(Error::Validate { err, .. }) if &err == "as cannot be used with multiple event streams"
6309        ),
6310        test_cml_offer_event_stream_capability_requested_not_from_framework(
6311            json!({
6312                "offer": [
6313                    {
6314                        "event_stream": ["capability_requested", "stopped"],
6315                        "from" : "parent",
6316                        "to": "#something"
6317                    },
6318                ]
6319            }),
6320            Err(Error::Validate { err, .. }) if &err == "\"#something\" is an \"offer\" target from \"parent\" but \"#something\" does not appear in \"children\" or \"collections\""
6321        ),
6322        test_cml_offer_event_stream_capability_requested_with_filter(
6323            json!({
6324                "offer": [
6325                    {
6326                        "event_stream": "capability_requested",
6327                        "from" : "framework",
6328                        "to": "#something",
6329                    },
6330                ]
6331            }),
6332            Err(Error::Validate { err, .. }) if &err == "\"#something\" is an \"offer\" target from \"framework\" but \"#something\" does not appear in \"children\" or \"collections\""
6333        ),
6334        test_cml_offer_event_stream_multiple_as(
6335            json!({
6336                "offer": [
6337                    {
6338                        "event_stream": ["started", "stopped"],
6339                        "from" : "framework",
6340                        "to": "#self",
6341                        "as": "something"
6342                    },
6343                ]
6344            }),
6345            Err(Error::Validate { err, .. }) if &err == "as cannot be used with multiple events"
6346        ),
6347        test_cml_expose_event_stream_from_self_no_span(
6348            json!({
6349                "expose": [
6350                    { "event_stream": ["started", "stopped"], "from" : "self" },
6351                ]
6352            }),
6353            Err(Error::Validate { err, .. }) if &err == "Cannot expose event_streams from self"
6354        ),
6355        test_cml_offer_event_stream_from_self_no_span(
6356            json!({
6357                "offer": [
6358                    { "event_stream": ["started", "stopped"], "from" : "self", "to": "#self" },
6359                ]
6360            }),
6361            Err(Error::Validate { err, .. }) if &err == "cannot offer an event_stream from self"
6362        ),
6363        test_cml_offer_event_stream_from_anything_else(
6364            json!({
6365                "offer": [
6366                    {
6367                        "event_stream": ["started", "stopped"],
6368                        "from" : "framework",
6369                        "to": "#self"
6370                    },
6371                ]
6372            }),
6373            Err(Error::Validate { err, .. }) if &err == "\"#self\" is an \"offer\" target from \"framework\" but \"#self\" does not appear in \"children\" or \"collections\""
6374        ),
6375        test_cml_expose_event_stream_to_framework_no_span(
6376            json!({
6377                "expose": [
6378                    {
6379                        "event_stream": ["started", "stopped"],
6380                        "from" : "self",
6381                        "to": "framework"
6382                    },
6383                ]
6384            }),
6385            Err(Error::Validate { err, .. }) if &err == "cannot expose an event_stream to framework"
6386        ),
6387        test_cml_expose_event_stream_scope_invalid_component(
6388            json!({
6389                "expose": [
6390                    {
6391                        "event_stream": ["started", "stopped"],
6392                        "from" : "framework",
6393                        "scope":["#invalid_component"]
6394                    },
6395                ]
6396            }),
6397            Err(Error::Validate { err, .. }) if &err == "event_stream scope invalid_component did not match a component or collection in this .cml file."
6398        ),
6399
6400        test_cml_use_from_self_no_span(
6401            json!({
6402                "use": [
6403                    {
6404                        "protocol": [ "bar_protocol", "baz_protocol" ],
6405                        "from": "self",
6406                    },
6407                    {
6408                        "directory": "foo_directory",
6409                        "from": "self",
6410                        "path": "/dir",
6411                        "rights": [ "r*" ],
6412                    },
6413                    {
6414                        "service": "foo_service",
6415                        "from": "self",
6416                    },
6417                    {
6418                        "config": "foo_config",
6419                        "type": "bool",
6420                        "key": "k",
6421                        "from": "self",
6422                    },
6423                ],
6424                "capabilities": [
6425                    {
6426                        "protocol": "bar_protocol",
6427                    },
6428                    {
6429                        "protocol": "baz_protocol",
6430                    },
6431                    {
6432                        "directory": "foo_directory",
6433                        "path": "/dir",
6434                        "rights": [ "r*" ],
6435                    },
6436                    {
6437                        "service": "foo_service",
6438                    },
6439                    {
6440                        "config": "foo_config",
6441                        "type": "bool",
6442                    },
6443                ]
6444            }),
6445            Ok(())
6446        ),
6447        test_cml_use_protocol_from_self_missing_no_span(
6448            json!({
6449                "use": [
6450                    {
6451                        "protocol": "foo_protocol",
6452                        "from": "self",
6453                    },
6454                ],
6455            }),
6456            Err(Error::Validate { err, .. }) if &err == "protocol \"foo_protocol\" is used from self, so it must be declared as a \"protocol\" in \"capabilities\""
6457        ),
6458        test_cml_use_numbered_handle_not_protocol_no_span(
6459            json!({
6460                "use": [
6461                    {
6462                        "runner": "foo",
6463                        "numbered_handle": 0xab,
6464                    },
6465                ],
6466            }),
6467            Err(Error::Validate { err, .. }) if &err == "`numbered_handle` is only supported for `use protocol`"
6468        ),
6469        test_cml_use_numbered_handle_and_path_no_span(
6470            json!({
6471                "use": [
6472                    {
6473                        "protocol": "foo",
6474                        "path": "/svc/foo",
6475                        "numbered_handle": 0xab,
6476                    },
6477                ],
6478            }),
6479            Err(Error::Validate { err, .. }) if &err == "`path` and `numbered_handle` are incompatible"
6480        ),
6481        test_cml_use_directory_from_self_missing_no_span(
6482            json!({
6483                "use": [
6484                    {
6485                        "directory": "foo_directory",
6486                        "from": "self",
6487                    },
6488                ],
6489            }),
6490            Err(Error::Validate { err, .. }) if &err == "directory \"foo_directory\" is used from self, so it must be declared as a \"directory\" in \"capabilities\""
6491        ),
6492        test_cml_use_service_from_self_missing_no_span(
6493            json!({
6494                "use": [
6495                    {
6496                        "service": "foo_service",
6497                        "from": "self",
6498                    },
6499                ],
6500            }),
6501            Err(Error::Validate { err, .. }) if &err == "service \"foo_service\" is used from self, so it must be declared as a \"service\" in \"capabilities\""
6502        ),
6503        test_cml_use_config_from_self_missing_no_span(
6504            json!({
6505                "use": [
6506                    {
6507                        "config": "foo_config",
6508                        "from": "self",
6509                    },
6510                ],
6511            }),
6512            Err(Error::Validate { err, .. }) if &err == "config \"foo_config\" is used from self, so it must be declared as a \"config\" in \"capabilities\""
6513        ),
6514        test_cml_use_from_self_missing_dictionary_no_span(
6515            json!({
6516                "use": [
6517                    {
6518                        "protocol": "foo_protocol",
6519                        "from": "self/dict/inner",
6520                    },
6521                ],
6522            }),
6523            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\""
6524        ),
6525        test_cml_use_event_stream_duplicate_no_span(
6526            json!({
6527                "use": [
6528                    { "event_stream": ["started", "started"], "from" : "parent" },
6529                ]
6530            }),
6531            Err(Error::Parse { err, .. }) if &err == "invalid value: array with duplicate element, expected a name or nonempty array of names, with unique elements"
6532        ),
6533        test_cml_use_event_stream_overlapping_path_no_span(
6534            json!({
6535                "use": [
6536                    { "directory": "foobarbaz", "path": "/foo/bar/baz", "rights": [ "r*" ] },
6537                    {
6538                        "event_stream": ["started"],
6539                        "path": "/foo/bar/baz/er",
6540                        "from": "parent",
6541                    },
6542                ],
6543            }),
6544            Err(Error::Validate { err, .. }) if &err == "directory \"/foo/bar/baz\" is a prefix of \"use\" target event_stream \"/foo/bar/baz/er\""
6545        ),
6546        test_cml_use_event_stream_invalid_path_no_span(
6547            json!({
6548                "use": [
6549                    {
6550                        "event_stream": ["started"],
6551                        "path": "my_stream",
6552                        "from": "parent",
6553                    },
6554                ],
6555            }),
6556            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"
6557        ),
6558        test_cml_use_event_stream_self_ref_no_span(
6559            json!({
6560                "use": [
6561                    {
6562                        "event_stream": ["started"],
6563                        "path": "/svc/my_stream",
6564                        "from": "self",
6565                    },
6566                ],
6567            }),
6568            Err(Error::Validate { err, .. }) if &err == "\"from: self\" cannot be used with \"event_stream\""
6569        ),
6570        test_cml_use_runner_debug_ref_no_span(
6571            json!({
6572                "use": [
6573                    {
6574                        "runner": "elf",
6575                        "from": "debug",
6576                    },
6577                ],
6578            }),
6579            Err(Error::Validate { err, .. }) if &err == "only \"protocol\" supports source from \"debug\""
6580        ),
6581        test_cml_use_runner_self_ref_no_span(
6582            json!({
6583                "use": [
6584                    {
6585                        "runner": "elf",
6586                        "from": "self",
6587                    },
6588                ],
6589            }),
6590            Err(Error::Validate { err, .. }) if &err == "\"from: self\" cannot be used with \"runner\""
6591        ),
6592        test_cml_use_missing_props_no_span(
6593            json!({
6594                "use": [ { "path": "/svc/fuchsia.logger.Log" } ]
6595            }),
6596            Err(Error::Validate { err, .. }) if &err == "`use` declaration is missing a capability keyword, one of: \"service\", \"protocol\", \"directory\", \"storage\", \"event_stream\", \"runner\", \"config\", \"dictionary\""
6597        ),
6598        test_cml_use_from_with_storage_no_span(
6599            json!({
6600                "use": [ { "storage": "cache", "from": "parent" } ]
6601            }),
6602            Err(Error::Validate { err, .. }) if &err == "\"from\" cannot be used with \"storage\""
6603        ),
6604        test_cml_use_invalid_from(
6605            json!({
6606                "use": [
6607                  { "protocol": "CoolFonts", "from": "bad" }
6608                ]
6609            }),
6610            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"
6611        ),
6612        test_cml_use_invalid_from_dictionary(
6613            json!({
6614                "use": [
6615                  { "protocol": "CoolFonts", "from": "bad/dict" }
6616                ]
6617            }),
6618            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"
6619        ),
6620        test_cml_use_from_missing_capability(
6621            json!({
6622                "use": [
6623                  { "protocol": "fuchsia.sys2.Admin", "from": "#mystorage" }
6624                ]
6625            }),
6626            Err(Error::Validate { err, .. }) if &err == "\"use\" source \"#mystorage\" does not appear in \"children\" or \"capabilities\""
6627        ),
6628        test_cml_use_bad_path(
6629            json!({
6630                "use": [
6631                    {
6632                        "protocol": ["CoolFonts", "FunkyFonts"],
6633                        "path": "/MyFonts"
6634                    }
6635                ]
6636            }),
6637            Err(Error::Validate { err, .. }) if &err == "\"path\" can only be specified when one `protocol` is supplied."
6638        ),
6639        test_cml_use_bad_duplicate_target_names_no_span(
6640            json!({
6641                "use": [
6642                  { "protocol": "fuchsia.component.Realm" },
6643                  { "protocol": "fuchsia.component.Realm" },
6644                ],
6645            }),
6646            Err(Error::Validate { err, .. }) if &err == "\"/svc/fuchsia.component.Realm\" is a duplicate \"use\" target protocol"
6647        ),
6648        test_cml_use_empty_protocols(
6649            json!({
6650                "use": [
6651                    {
6652                        "protocol": [],
6653                    },
6654                ],
6655            }),
6656            Err(Error::Parse { err, .. }) if &err == "invalid length 0, expected a name or nonempty array of names, with unique elements"
6657        ),
6658        test_cml_use_bad_subdir(
6659            json!({
6660                "use": [
6661                  {
6662                    "directory": "config",
6663                    "path": "/config",
6664                    "from": "parent",
6665                    "rights": [ "r*" ],
6666                    "subdir": "/",
6667                  },
6668                ]
6669            }),
6670            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"/\", expected a path with no leading `/` and non-empty segments"
6671        ),
6672        test_cml_use_resolver_fails(
6673            json!({
6674                "use": [
6675                    {
6676                        "resolver": "pkg_resolver",
6677                    },
6678                ]
6679            }),
6680            Err(Error::Parse { err, .. }) if err.starts_with("unknown field `resolver`, expected one of")
6681        ),
6682
6683        test_cml_use_disallows_nested_dirs_directory_no_span(
6684            json!({
6685                "use": [
6686                    { "directory": "foobar", "path": "/foo/bar", "rights": [ "r*" ] },
6687                    { "directory": "foobarbaz", "path": "/foo/bar/baz", "rights": [ "r*" ] },
6688                ],
6689            }),
6690            Err(Error::Validate { err, .. }) if &err == "directory \"/foo/bar\" is a prefix of \"use\" target directory \"/foo/bar/baz\""
6691        ),
6692        test_cml_use_disallows_nested_dirs_storage_no_span(
6693            json!({
6694                "use": [
6695                    { "storage": "foobar", "path": "/foo/bar" },
6696                    { "storage": "foobarbaz", "path": "/foo/bar/baz" },
6697                ],
6698            }),
6699            Err(Error::Validate { err, .. }) if &err == "storage \"/foo/bar\" is a prefix of \"use\" target storage \"/foo/bar/baz\""
6700        ),
6701        test_cml_use_disallows_nested_dirs_directory_and_storage_no_span(
6702            json!({
6703                "use": [
6704                    { "directory": "foobar", "path": "/foo/bar", "rights": [ "r*" ] },
6705                    { "storage": "foobarbaz", "path": "/foo/bar/baz" },
6706                ],
6707            }),
6708            Err(Error::Validate { err, .. }) if &err == "directory \"/foo/bar\" is a prefix of \"use\" target storage \"/foo/bar/baz\""
6709        ),
6710        test_cml_use_disallows_common_prefixes_service_no_span(
6711            json!({
6712                "use": [
6713                    { "directory": "foobar", "path": "/foo/bar", "rights": [ "r*" ] },
6714                    { "protocol": "fuchsia", "path": "/foo/bar/fuchsia" },
6715                ],
6716            }),
6717            Err(Error::Validate { err, .. }) if &err == "directory \"/foo/bar\" is a prefix of \"use\" target protocol \"/foo/bar/fuchsia\""
6718        ),
6719        test_cml_use_disallows_common_prefixes_protocol_no_span(
6720            json!({
6721                "use": [
6722                    { "directory": "foobar", "path": "/foo/bar", "rights": [ "r*" ] },
6723                    { "protocol": "fuchsia", "path": "/foo/bar/fuchsia.2" },
6724                ],
6725            }),
6726            Err(Error::Validate { err, .. }) if &err == "directory \"/foo/bar\" is a prefix of \"use\" target protocol \"/foo/bar/fuchsia.2\""
6727        ),
6728        test_cml_use_disallows_pkg_conflicts_for_directories(
6729            json!({
6730                "use": [
6731                    { "directory": "dir", "path": "/pkg/dir", "rights": [ "r*" ] },
6732                ],
6733            }),
6734            Err(Error::Validate { err, .. }) if &err == "directory \"/pkg/dir\" conflicts with the protected path \"/pkg\", please use this capability with a different path"
6735        ),
6736        test_cml_use_disallows_pkg_conflicts_for_protocols(
6737            json!({
6738                "use": [
6739                    { "protocol": "prot", "path": "/pkg/protocol" },
6740                ],
6741            }),
6742            Err(Error::Validate { err, .. }) if &err == "protocol \"/pkg/protocol\" conflicts with the protected path \"/pkg\", please use this capability with a different path"
6743        ),
6744        test_cml_use_disallows_pkg_conflicts_for_storage(
6745            json!({
6746                "use": [
6747                    { "storage": "store", "path": "/pkg/storage" },
6748                ],
6749            }),
6750            Err(Error::Validate { err, .. }) if &err == "storage \"/pkg/storage\" conflicts with the protected path \"/pkg\", please use this capability with a different path"
6751        ),
6752        test_cml_use_disallows_filter_on_non_events_no_span(
6753            json!({
6754                "use": [
6755                    { "directory": "foobar", "path": "/foo/bar", "rights": [ "r*" ], "filter": {"path": "/diagnostics"} },
6756                ],
6757            }),
6758            Err(Error::Validate { err, .. }) if &err == "\"filter\" can only be used with \"event_stream\""
6759        ),
6760        test_cml_availability_not_supported_for_event_streams_no_span(
6761            json!({
6762                "use": [
6763                    {
6764                        "event_stream": ["destroyed"],
6765                        "from": "parent",
6766                        "availability": "optional",
6767                    }
6768                ]
6769            }),
6770            Err(Error::Validate { err, .. }) if &err == "\"availability\" cannot be used with \"event_stream\""
6771        ),
6772        test_cml_use_from_parent_weak(
6773            json!({
6774                "use": [
6775                    {
6776                        "protocol": "fuchsia.parent.Protocol",
6777                        "from": "parent",
6778                        "dependency": "weak",
6779                    },
6780                ],
6781            }),
6782            Err(Error::Validate { err, .. }) if &err == "Only `use` from children can have dependency: \"weak\""
6783        ),
6784        test_cml_use_from_child_weak(
6785            json!({
6786                "children": [
6787                    {
6788                        "name": "logger",
6789                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
6790                    },
6791                ],
6792                "use": [
6793                    {
6794                        "protocol": "fuchsia.child.Protocol",
6795                        "from": "#logger",
6796                        "dependency": "weak",
6797                    },
6798                ],
6799            }),
6800            Ok(())
6801        ),
6802        test_cml_use_from_child_dictionary_weak(
6803            json!({
6804                "children": [
6805                    {
6806                        "name": "logger",
6807                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
6808                    },
6809                ],
6810                "use": [
6811                    {
6812                        "protocol": "fuchsia.child.Protocol",
6813                        "from": "#logger/dictionary",
6814                        "dependency": "weak",
6815                    },
6816                ],
6817            }),
6818            Ok(())
6819        ),
6820        test_cml_use_numbered_handle_from_same_protocol_ok(
6821            json!({
6822                "use": [
6823                    {
6824                        "protocol": "foo",
6825                    },
6826                    {
6827                        "protocol": "foo",
6828                        "numbered_handle": 0xab,
6829                    },
6830                    {
6831                        "protocol": "foo",
6832                        "numbered_handle": 0xac,
6833                    },
6834                ],
6835            }),
6836            Ok(())
6837        ),
6838        test_cml_use_numbered_handle_not_a_number(
6839            json!({
6840                "use": [
6841                    {
6842                        "protocol": "foo",
6843                        "numbered_handle": "0xab",
6844                    },
6845                ],
6846            }),
6847            Err(Error::Parse { err, .. }) if &err == "error parsing number"
6848        ),
6849        test_cml_use_numbered_handle_out_of_range(
6850            json!({
6851                "use": [
6852                    {
6853                        "protocol": "foo",
6854                        "numbered_handle": 256,
6855                    },
6856                ],
6857            }),
6858            Err(Error::Parse { err, .. }) if &err == "invalid value: integer `256`, expected a uint8 from zircon/processargs.h"
6859        ),
6860
6861        // expose
6862        test_cml_expose(
6863            json!({
6864                "expose": [
6865                    {
6866                        "protocol": "A",
6867                        "from": "self",
6868                    },
6869                    {
6870                        "protocol": ["B", "C"],
6871                        "from": "self",
6872                    },
6873                    {
6874                        "protocol": "D",
6875                        "from": "#mystorage",
6876                    },
6877                    {
6878                        "directory": "blobfs",
6879                        "from": "self",
6880                        "rights": ["r*"],
6881                        "subdir": "blob",
6882                    },
6883                    { "directory": "data", "from": "framework" },
6884                    { "runner": "elf", "from": "#logger",  },
6885                    { "resolver": "pkg_resolver", "from": "#logger" },
6886                ],
6887                "capabilities": [
6888                    { "protocol": ["A", "B", "C"] },
6889                    {
6890                        "directory": "blobfs",
6891                        "path": "/blobfs",
6892                        "rights": ["rw*"],
6893                    },
6894                    {
6895                        "storage": "mystorage",
6896                        "from": "self",
6897                        "backing_dir": "blobfs",
6898                        "storage_id": "static_instance_id_or_moniker",
6899                    }
6900                ],
6901                "children": [
6902                    {
6903                        "name": "logger",
6904                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
6905                    },
6906                ]
6907            }),
6908            Ok(())
6909        ),
6910        test_cml_expose_all_valid_chars(
6911            json!({
6912                "expose": [
6913                    {
6914                        "protocol": "fuchsia.logger.Log",
6915                        "from": "#abcdefghijklmnopqrstuvwxyz0123456789_-.",
6916                    },
6917                ],
6918                "children": [
6919                    {
6920                        "name": "abcdefghijklmnopqrstuvwxyz0123456789_-.",
6921                        "url": "https://www.google.com/gmail"
6922                    },
6923                ],
6924            }),
6925            Ok(())
6926        ),
6927        test_cml_expose_missing_props(
6928            json!({
6929                "expose": [ {} ]
6930            }),
6931            Err(Error::Parse { err, .. }) if &err == "missing field `from`"
6932        ),
6933        test_cml_expose_missing_from(
6934            json!({
6935                "expose": [
6936                    { "protocol": "fuchsia.logger.Log", "from": "#missing" },
6937                ],
6938            }),
6939            Err(Error::Validate { err, .. }) if &err == "\"expose\" source \"#missing\" does not appear in \"children\" or \"capabilities\""
6940        ),
6941        test_cml_expose_duplicate_target_names(
6942            json!({
6943                "capabilities": [
6944                    { "protocol": "logger" },
6945                ],
6946                "expose": [
6947                    { "protocol": "logger", "from": "self", "as": "thing" },
6948                    { "directory": "thing", "from": "#child" , "rights": ["rx*"] },
6949                ],
6950                "children": [
6951                    {
6952                        "name": "child",
6953                        "url": "fuchsia-pkg://fuchsia.com/pkg#comp.cm",
6954                    },
6955                ],
6956            }),
6957            Err(Error::Validate { err, .. }) if &err == "\"thing\" is a duplicate \"expose\" target capability for \"parent\""
6958        ),
6959        test_cml_expose_invalid_multiple_from(
6960            json!({
6961                    "capabilities": [
6962                        { "protocol": "fuchsia.logger.Log" },
6963                    ],
6964                    "expose": [
6965                        {
6966                            "protocol": "fuchsia.logger.Log",
6967                            "from": [ "self", "#logger" ],
6968                        },
6969                    ],
6970                    "children": [
6971                        {
6972                            "name": "logger",
6973                            "url": "fuchsia-pkg://fuchsia.com/logger#meta/logger.cm",
6974                        },
6975                    ]
6976                }),
6977            Err(Error::Validate { err, .. }) if &err == "\"protocol\" capabilities cannot have multiple \"from\" clauses"
6978        ),
6979        test_cml_expose_from_missing_named_source(
6980            json!({
6981                    "expose": [
6982                        {
6983                            "protocol": "fuchsia.logger.Log",
6984                            "from": "#does-not-exist",
6985                        },
6986                    ],
6987                }),
6988            Err(Error::Validate { err, .. }) if &err == "\"expose\" source \"#does-not-exist\" does not appear in \"children\" or \"capabilities\""
6989        ),
6990        test_cml_expose_bad_from(
6991            json!({
6992                "expose": [ {
6993                    "protocol": "fuchsia.logger.Log", "from": "parent"
6994                } ]
6995            }),
6996            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"parent\", expected one or an array of \"framework\", \"self\", \"#<child-name>\", or a dictionary path"
6997        ),
6998        // if "as" is specified, only 1 array item is allowed.
6999        test_cml_expose_bad_as(
7000            json!({
7001                "expose": [
7002                    {
7003                        "protocol": ["A", "B"],
7004                        "from": "#echo_server",
7005                        "as": "thing"
7006                    },
7007                ],
7008                "children": [
7009                    {
7010                        "name": "echo_server",
7011                        "url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo_server.cm"
7012                    }
7013                ]
7014            }),
7015            Err(Error::Validate { err, .. }) if &err == "\"as\" can only be specified when one `protocol` is supplied."
7016        ),
7017        test_cml_expose_empty_protocols(
7018            json!({
7019                "expose": [
7020                    {
7021                        "protocol": [],
7022                        "from": "#child",
7023                        "as": "thing"
7024                    },
7025                ],
7026                "children": [
7027                    {
7028                        "name": "child",
7029                        "url": "fuchsia-pkg://fuchsia.com/pkg#comp.cm",
7030                    },
7031                ],
7032            }),
7033            Err(Error::Parse { err, .. }) if &err == "invalid length 0, expected a name or nonempty array of names, with unique elements"
7034        ),
7035        test_cml_expose_bad_subdir(
7036            json!({
7037                "expose": [
7038                    {
7039                        "directory": "blobfs",
7040                        "from": "self",
7041                        "rights": ["r*"],
7042                        "subdir": "/",
7043                    },
7044                ]
7045            }),
7046            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"/\", expected a path with no leading `/` and non-empty segments"
7047        ),
7048        test_cml_expose_invalid_subdir_to_framework_no_span(
7049            json!({
7050                "capabilities": [
7051                    {
7052                        "directory": "foo",
7053                        "rights": ["r*"],
7054                        "path": "/foo",
7055                    },
7056                ],
7057                "expose": [
7058                    {
7059                        "directory": "foo",
7060                        "from": "self",
7061                        "to": "framework",
7062                        "subdir": "blob",
7063                    },
7064                ],
7065                "children": [
7066                    {
7067                        "name": "child",
7068                        "url": "fuchsia-pkg://fuchsia.com/pkg#comp.cm",
7069                    },
7070                ],
7071            }),
7072            Err(Error::Validate { err, .. }) if &err == "`subdir` is not supported for expose to framework. Directly expose the subdirectory instead."
7073        ),
7074        test_cml_expose_from_self_no_span(
7075            json!({
7076                "expose": [
7077                    {
7078                        "protocol": "foo_protocol",
7079                        "from": "self",
7080                    },
7081                    {
7082                        "protocol": [ "bar_protocol", "baz_protocol" ],
7083                        "from": "self",
7084                    },
7085                    {
7086                        "directory": "foo_directory",
7087                        "from": "self",
7088                    },
7089                    {
7090                        "runner": "foo_runner",
7091                        "from": "self",
7092                    },
7093                    {
7094                        "resolver": "foo_resolver",
7095                        "from": "self",
7096                    },
7097                ],
7098                "capabilities": [
7099                    {
7100                        "protocol": "foo_protocol",
7101                    },
7102                    {
7103                        "protocol": "bar_protocol",
7104                    },
7105                    {
7106                        "protocol": "baz_protocol",
7107                    },
7108                    {
7109                        "directory": "foo_directory",
7110                        "path": "/dir",
7111                        "rights": [ "r*" ],
7112                    },
7113                    {
7114                        "runner": "foo_runner",
7115                        "path": "/svc/runner",
7116                    },
7117                    {
7118                        "resolver": "foo_resolver",
7119                        "path": "/svc/resolver",
7120                    },
7121                ]
7122            }),
7123            Ok(())
7124        ),
7125        test_cml_expose_protocol_from_self_missing_no_span(
7126            json!({
7127                "expose": [
7128                    {
7129                        "protocol": "pkg_protocol",
7130                        "from": "self",
7131                    },
7132                ],
7133            }),
7134            Err(Error::Validate { err, .. }) if &err == "protocol \"pkg_protocol\" is exposed from self, so it must be declared as a \"protocol\" in \"capabilities\""
7135        ),
7136        test_cml_expose_protocol_from_self_missing_multiple_no_span(
7137            json!({
7138                "expose": [
7139                    {
7140                        "protocol": [ "foo_protocol", "bar_protocol" ],
7141                        "from": "self",
7142                    },
7143                ],
7144            }),
7145            Err(Error::Validate { err, .. }) if &err == "protocol \"foo_protocol\" is exposed from self, so it must be declared as a \"protocol\" in \"capabilities\""
7146        ),
7147        test_cml_expose_directory_from_self_missing_no_span(
7148            json!({
7149                "expose": [
7150                    {
7151                        "directory": "pkg_directory",
7152                        "from": "self",
7153                    },
7154                ],
7155            }),
7156            Err(Error::Validate { err, .. }) if &err == "directory \"pkg_directory\" is exposed from self, so it must be declared as a \"directory\" in \"capabilities\""
7157        ),
7158        test_cml_expose_service_from_self_missing_no_span(
7159            json!({
7160                "expose": [
7161                    {
7162                        "service": "pkg_service",
7163                        "from": "self",
7164                    },
7165                ],
7166            }),
7167            Err(Error::Validate { err, .. }) if &err == "service \"pkg_service\" is exposed from self, so it must be declared as a \"service\" in \"capabilities\""
7168        ),
7169        test_cml_expose_runner_from_self_missing_no_span(
7170            json!({
7171                "expose": [
7172                    {
7173                        "runner": "dart",
7174                        "from": "self",
7175                    },
7176                ],
7177            }),
7178            Err(Error::Validate { err, .. }) if &err == "runner \"dart\" is exposed from self, so it must be declared as a \"runner\" in \"capabilities\""
7179        ),
7180        test_cml_expose_resolver_from_self_missing_no_span(
7181            json!({
7182                "expose": [
7183                    {
7184                        "resolver": "pkg_resolver",
7185                        "from": "self",
7186                    },
7187                ],
7188            }),
7189            Err(Error::Validate { err, .. }) if &err == "resolver \"pkg_resolver\" is exposed from self, so it must be declared as a \"resolver\" in \"capabilities\""
7190        ),
7191        test_cml_expose_from_self_missing_dictionary_no_span(
7192            json!({
7193                "expose": [
7194                    {
7195                        "protocol": "foo_protocol",
7196                        "from": "self/dict/inner",
7197                    },
7198                ],
7199            }),
7200            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\""
7201        ),
7202        test_cml_expose_from_dictionary_invalid(
7203            json!({
7204                "expose": [
7205                    {
7206                        "protocol": "pkg_protocol",
7207                        "from": "bad/a",
7208                    },
7209                ],
7210            }),
7211            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"
7212        ),
7213        test_cml_expose_from_dictionary_parent_no_span(
7214            json!({
7215                "expose": [
7216                    {
7217                        "protocol": "pkg_protocol",
7218                        "from": "parent/a",
7219                    },
7220                ],
7221            }),
7222            Err(Error::Validate { err, .. }) if &err == "`expose` dictionary path must begin with `self` or `#<child-name>`"
7223        ),
7224        test_cml_expose_protocol_from_collection_invalid_no_span(
7225            json!({
7226                "collections": [ {
7227                    "name": "coll",
7228                    "durability": "transient",
7229                } ],
7230                "expose": [
7231                    { "protocol": "fuchsia.logger.Log", "from": "#coll" },
7232                ]
7233            }),
7234            Err(Error::Validate { err, .. }) if &err == "\"expose\" source \"#coll\" does not appear in \"children\" or \"capabilities\""
7235        ),
7236        test_cml_expose_directory_from_collection_invalid_no_span(
7237            json!({
7238                "collections": [ {
7239                    "name": "coll",
7240                    "durability": "transient",
7241                } ],
7242                "expose": [
7243                    { "directory": "temp", "from": "#coll" },
7244                ]
7245            }),
7246            Err(Error::Validate { err, .. }) if &err == "\"expose\" source \"#coll\" does not appear in \"children\""
7247        ),
7248        test_cml_expose_runner_from_collection_invalid_no_span(
7249            json!({
7250                "collections": [ {
7251                    "name": "coll",
7252                    "durability": "transient",
7253                } ],
7254                "expose": [
7255                    { "runner": "elf", "from": "#coll" },
7256                ]
7257            }),
7258            Err(Error::Validate { err, .. }) if &err == "\"expose\" source \"#coll\" does not appear in \"children\""
7259        ),
7260        test_cml_expose_resolver_from_collection_invalid_no_span(
7261            json!({
7262                "collections": [ {
7263                    "name": "coll",
7264                    "durability": "transient",
7265                } ],
7266                "expose": [
7267                    { "resolver": "base", "from": "#coll" },
7268                ]
7269            }),
7270            Err(Error::Validate { err, .. }) if &err == "\"expose\" source \"#coll\" does not appear in \"children\""
7271        ),
7272        // offer
7273        test_cml_offer(
7274            json!({
7275                "offer": [
7276                    {
7277                        "protocol": "fuchsia.fonts.LegacyProvider",
7278                        "from": "parent",
7279                        "to": [ "#echo_server" ],
7280                        "dependency": "weak"
7281                    },
7282                    {
7283                        "protocol": "fuchsia.sys2.StorageAdmin",
7284                        "from": "#data",
7285                        "to": [ "#echo_server" ]
7286                    },
7287                    {
7288                        "protocol": [
7289                            "fuchsia.settings.Accessibility",
7290                            "fuchsia.ui.scenic.Scenic"
7291                        ],
7292                        "from": "parent",
7293                        "to": [ "#echo_server" ],
7294                        "dependency": "strong"
7295                    },
7296                    {
7297                        "directory": "assets",
7298                        "from": "self",
7299                        "to": [ "#echo_server" ],
7300                        "rights": ["r*"]
7301                    },
7302                    {
7303                        "directory": "index",
7304                        "subdir": "files",
7305                        "from": "parent",
7306                        "to": [ "#modular" ],
7307                        "dependency": "weak"
7308                    },
7309                    {
7310                        "directory": "config",
7311                        "from": "framework",
7312                        "to": [ "#modular" ],
7313                        "as": "config",
7314                        "dependency": "strong"
7315                    },
7316                    {
7317                        "storage": "data",
7318                        "from": "self",
7319                        "to": [ "#modular", "#logger" ]
7320                    },
7321                    {
7322                        "runner": "elf",
7323                        "from": "parent",
7324                        "to": [ "#modular", "#logger" ]
7325                    },
7326                    {
7327                        "resolver": "pkg_resolver",
7328                        "from": "parent",
7329                        "to": [ "#modular" ],
7330                    },
7331                ],
7332                "children": [
7333                    {
7334                        "name": "logger",
7335                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
7336                    },
7337                    {
7338                        "name": "echo_server",
7339                        "url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo_server.cm"
7340                    },
7341                ],
7342                "collections": [
7343                    {
7344                        "name": "modular",
7345                        "durability": "transient",
7346                    },
7347                ],
7348                "capabilities": [
7349                    {
7350                        "directory": "assets",
7351                        "path": "/data/assets",
7352                        "rights": [ "rw*" ],
7353                    },
7354                    {
7355                        "storage": "data",
7356                        "from": "parent",
7357                        "backing_dir": "minfs",
7358                        "storage_id": "static_instance_id_or_moniker",
7359                    },
7360                ],
7361            }),
7362            Ok(())
7363        ),
7364        test_cml_offer_all_valid_chars(
7365            json!({
7366                "offer": [
7367                    {
7368                        "protocol": "fuchsia.logger.Log",
7369                        "from": "#abcdefghijklmnopqrstuvwxyz0123456789_-from",
7370                        "to": [ "#abcdefghijklmnopqrstuvwxyz0123456789_-to" ],
7371                    },
7372                ],
7373                "children": [
7374                    {
7375                        "name": "abcdefghijklmnopqrstuvwxyz0123456789_-from",
7376                        "url": "https://www.google.com/gmail"
7377                    },
7378                    {
7379                        "name": "abcdefghijklmnopqrstuvwxyz0123456789_-to",
7380                        "url": "https://www.google.com/gmail"
7381                    },
7382                ],
7383                "capabilities": [
7384                    {
7385                        "storage": "abcdefghijklmnopqrstuvwxyz0123456789_-storage",
7386                        "from": "#abcdefghijklmnopqrstuvwxyz0123456789_-from",
7387                        "backing_dir": "example",
7388                        "storage_id": "static_instance_id_or_moniker",
7389                    }
7390                ]
7391            }),
7392            Ok(())
7393        ),
7394        test_cml_offer_singleton_to (
7395            json!({
7396                "offer": [
7397                    {
7398                        "protocol": "fuchsia.fonts.LegacyProvider",
7399                        "from": "parent",
7400                        "to": "#echo_server",
7401                        "dependency": "weak"
7402                    },
7403                ],
7404                "children": [
7405                    {
7406                        "name": "echo_server",
7407                        "url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo_server.cm"
7408                    },
7409                ],
7410            }),
7411            Ok(())
7412        ),
7413        test_cml_offer_missing_props(
7414            json!({
7415                "offer": [ {} ]
7416            }),
7417            Err(Error::Parse { err, .. }) if &err == "missing field `from`"
7418        ),
7419        test_cml_offer_missing_from(
7420            json!({
7421                    "offer": [
7422                        {
7423                            "protocol": "fuchsia.logger.Log",
7424                            "from": "#missing",
7425                            "to": [ "#echo_server" ],
7426                        },
7427                    ],
7428                    "children": [
7429                        {
7430                            "name": "echo_server",
7431                            "url": "fuchsia-pkg://fuchsia.com/echo_server#meta/echo_server.cm",
7432                        },
7433                    ],
7434                }),
7435            Err(Error::Validate { err, .. }) if &err == "\"offer\" source \"#missing\" does not appear in \"children\" or \"capabilities\""
7436        ),
7437        test_cml_storage_offer_from_child_no_span(
7438            json!({
7439                    "offer": [
7440                        {
7441                            "storage": "cache",
7442                            "from": "#storage_provider",
7443                            "to": [ "#echo_server" ],
7444                        },
7445                    ],
7446                    "children": [
7447                        {
7448                            "name": "echo_server",
7449                            "url": "fuchsia-pkg://fuchsia.com/echo_server#meta/echo_server.cm",
7450                        },
7451                        {
7452                            "name": "storage_provider",
7453                            "url": "fuchsia-pkg://fuchsia.com/storage_provider#meta/storage_provider.cm",
7454                        },
7455                    ],
7456                }),
7457            Err(Error::Validate { err, .. }) if &err == "Storage \"cache\" is offered from a child, but storage capabilities cannot be exposed"
7458        ),
7459        test_cml_offer_bad_from(
7460            json!({
7461                    "offer": [ {
7462                        "protocol": "fuchsia.logger.Log",
7463                        "from": "#invalid@",
7464                        "to": [ "#echo_server" ],
7465                    } ]
7466                }),
7467            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"
7468        ),
7469        test_cml_offer_invalid_multiple_from(
7470            json!({
7471                    "offer": [
7472                        {
7473                            "protocol": "fuchsia.logger.Log",
7474                            "from": [ "parent", "#logger" ],
7475                            "to": [ "#echo_server" ],
7476                        },
7477                    ],
7478                    "children": [
7479                        {
7480                            "name": "logger",
7481                            "url": "fuchsia-pkg://fuchsia.com/logger#meta/logger.cm",
7482                        },
7483                        {
7484                            "name": "echo_server",
7485                            "url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo_server.cm",
7486                        },
7487                    ]
7488                }),
7489            Err(Error::Validate { err, .. }) if &err == "\"protocol\" capabilities cannot have multiple \"from\" clauses"
7490        ),
7491        test_cml_offer_from_missing_named_source(
7492            json!({
7493                    "offer": [
7494                        {
7495                            "protocol": "fuchsia.logger.Log",
7496                            "from": "#does-not-exist",
7497                            "to": ["#echo_server" ],
7498                        },
7499                    ],
7500                    "children": [
7501                        {
7502                            "name": "echo_server",
7503                            "url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo_server.cm",
7504                        },
7505                    ]
7506                }),
7507            Err(Error::Validate { err, .. }) if &err == "\"offer\" source \"#does-not-exist\" does not appear in \"children\" or \"capabilities\""
7508        ),
7509        test_cml_offer_protocol_from_collection_invalid(
7510            json!({
7511                "collections": [ {
7512                    "name": "coll",
7513                    "durability": "transient",
7514                } ],
7515                "children": [ {
7516                    "name": "echo_server",
7517                    "url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo_server.cm",
7518                } ],
7519                "offer": [
7520                    { "protocol": "fuchsia.logger.Log", "from": "#coll", "to": [ "#echo_server" ] },
7521                ]
7522            }),
7523            Err(Error::Validate { err, .. }) if &err == "\"offer\" source \"#coll\" does not appear in \"children\" or \"capabilities\""
7524        ),
7525        test_cml_offer_directory_from_collection_invalid(
7526            json!({
7527                "collections": [ {
7528                    "name": "coll",
7529                    "durability": "transient",
7530                } ],
7531                "children": [ {
7532                    "name": "echo_server",
7533                    "url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo_server.cm",
7534                } ],
7535                "offer": [
7536                    { "directory": "temp", "from": "#coll", "to": [ "#echo_server" ] },
7537                ]
7538            }),
7539            Err(Error::Validate { err, .. }) if &err == "\"offer\" source \"#coll\" does not appear in \"children\""
7540        ),
7541        test_cml_offer_storage_from_collection_invalid_no_span(
7542            json!({
7543                "collections": [ {
7544                    "name": "coll",
7545                    "durability": "transient",
7546                } ],
7547                "children": [ {
7548                    "name": "echo_server",
7549                    "url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo_server.cm",
7550                } ],
7551                "offer": [
7552                    { "storage": "cache", "from": "#coll", "to": [ "#echo_server" ] },
7553                ]
7554            }),
7555            Err(Error::Validate { err, .. }) if &err == "Storage \"cache\" is offered from a child, but storage capabilities cannot be exposed"
7556        ),
7557        test_cml_offer_runner_from_collection_invalid(
7558            json!({
7559                "collections": [ {
7560                    "name": "coll",
7561                    "durability": "transient",
7562                } ],
7563                "children": [ {
7564                    "name": "echo_server",
7565                    "url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo_server.cm",
7566                } ],
7567                "offer": [
7568                    { "runner": "elf", "from": "#coll", "to": [ "#echo_server" ] },
7569                ]
7570            }),
7571            Err(Error::Validate { err, .. }) if &err == "\"offer\" source \"#coll\" does not appear in \"children\""
7572        ),
7573        test_cml_offer_resolver_from_collection_invalid(
7574            json!({
7575                "collections": [ {
7576                    "name": "coll",
7577                    "durability": "transient",
7578                } ],
7579                "children": [ {
7580                    "name": "echo_server",
7581                    "url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo_server.cm",
7582                } ],
7583                "offer": [
7584                    { "resolver": "base", "from": "#coll", "to": [ "#echo_server" ] },
7585                ]
7586            }),
7587            Err(Error::Validate { err, .. }) if &err == "\"offer\" source \"#coll\" does not appear in \"children\""
7588        ),
7589        test_cml_offer_from_dictionary_invalid(
7590            json!({
7591                "offer": [
7592                    {
7593                        "protocol": "pkg_protocol",
7594                        "from": "bad/a",
7595                        "to": "#child",
7596                    },
7597                ],
7598                "children": [
7599                    {
7600                        "name": "child",
7601                        "url": "fuchsia-pkg://child",
7602                    },
7603                ],
7604            }),
7605            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"
7606        ),
7607        test_cml_offer_to_non_dictionary(
7608            json!({
7609                "offer": [
7610                    {
7611                        "protocol": "p",
7612                        "from": "parent",
7613                        "to": "self/dict",
7614                    },
7615                ],
7616                "capabilities": [
7617                    {
7618                        "protocol": "dict",
7619                    },
7620                ],
7621            }),
7622            Err(Error::Validate { err, .. }) if &err == "\"offer\" has dictionary target \
7623            \"self/dict\" but \"dict\" is not a dictionary capability defined by \
7624            this component"
7625        ),
7626
7627        test_cml_offer_empty_targets(
7628            json!({
7629                "offer": [
7630                    {
7631                        "protocol": "fuchsia.logger.Log",
7632                        "from": "#child",
7633                        "to": []
7634                    },
7635                ],
7636                "children": [
7637                    {
7638                        "name": "child",
7639                        "url": "fuchsia-pkg://fuchsia.com/pkg#comp.cm",
7640                    },
7641                ],
7642            }),
7643            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"
7644        ),
7645        test_cml_offer_duplicate_targets(
7646            json!({
7647                "offer": [ {
7648                    "protocol": "fuchsia.logger.Log",
7649                    "from": "#logger",
7650                    "to": ["#a", "#a"]
7651                } ]
7652            }),
7653            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"
7654        ),
7655        test_cml_offer_target_missing_props(
7656            json!({
7657                "offer": [ {
7658                    "protocol": "fuchsia.logger.Log",
7659                    "from": "#logger",
7660                    "as": "fuchsia.logger.SysLog",
7661                } ]
7662            }),
7663            Err(Error::Parse { err, .. }) if &err == "missing field `to`"
7664        ),
7665        test_cml_offer_target_missing_to(
7666            json!({
7667                "offer": [ {
7668                    "protocol": "fuchsia.logger.Log",
7669                    "from": "#logger",
7670                    "to": [ "#missing" ],
7671                } ],
7672                "children": [ {
7673                    "name": "logger",
7674                    "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
7675                } ]
7676            }),
7677            Err(Error::Validate { err, .. }) if &err == "\"#missing\" is an \"offer\" target from \"#logger\" but \"#missing\" does not appear in \"children\" or \"collections\""
7678        ),
7679        test_cml_offer_target_bad_to(
7680            json!({
7681                "offer": [ {
7682                    "protocol": "fuchsia.logger.Log",
7683                    "from": "#logger",
7684                    "to": [ "self" ],
7685                    "as": "fuchsia.logger.SysLog",
7686                } ]
7687            }),
7688            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"self\", expected \"#<child-name>\", \"#<collection-name>\", or \"self/<dictionary>\""
7689        ),
7690        test_cml_offer_empty_protocols(
7691            json!({
7692                "offer": [
7693                    {
7694                        "protocol": [],
7695                        "from": "parent",
7696                        "to": [ "#echo_server" ],
7697                        "as": "thing"
7698                    },
7699                ],
7700            }),
7701            Err(Error::Parse { err, .. }) if &err == "invalid length 0, expected a name or nonempty array of names, with unique elements"
7702        ),
7703        test_cml_offer_target_equals_from(
7704            json!({
7705                "children": [
7706                    {
7707                        "name": "child",
7708                        "url": "fuchsia-pkg://fuchsia.com/child#meta/child.cm",
7709                    },
7710                ],
7711                "offer": [
7712                    {
7713                        "protocol": "fuchsia.example.Protocol",
7714                        "from": "#child",
7715                        "to": [ "#child" ],
7716                    },
7717                ],
7718            }),
7719            Err(Error::Validate { err, .. }) if &err == "Offer target \"#child\" is same as source"
7720        ),
7721        test_cml_offer_target_equals_from_weak(
7722            json!({
7723                "children": [
7724                    {
7725                        "name": "child",
7726                        "url": "fuchsia-pkg://fuchsia.com/child#meta/child.cm",
7727                    },
7728                ],
7729                "offer": [
7730                    {
7731                        "protocol": "fuchsia.example.Protocol",
7732                        "from": "#child",
7733                        "to": [ "#child" ],
7734                        "dependency": "weak",
7735                    },
7736                    {
7737                        "directory": "data",
7738                        "from": "#child",
7739                        "to": [ "#child" ],
7740                        "dependency": "weak",
7741                    },
7742                ],
7743            }),
7744            Ok(())
7745        ),
7746        test_cml_storage_offer_target_equals_from(
7747            json!({
7748                "offer": [ {
7749                    "storage": "minfs",
7750                    "from": "self",
7751                    "to": [ "#logger" ],
7752                } ],
7753                "children": [ {
7754                    "name": "logger",
7755                    "url": "fuchsia-pkg://fuchsia.com/logger#meta/logger.cm",
7756                } ],
7757                "capabilities": [ {
7758                    "storage": "minfs",
7759                    "from": "#logger",
7760                    "backing_dir": "minfs-dir",
7761                    "storage_id": "static_instance_id_or_moniker",
7762                } ],
7763            }),
7764            Err(Error::Validate { err, .. }) if &err == "Storage offer target \"#logger\" is same as source"
7765        ),
7766        test_cml_offer_duplicate_target_names(
7767            json!({
7768                "offer": [
7769                    {
7770                        "protocol": "logger",
7771                        "from": "parent",
7772                        "to": [ "#echo_server" ],
7773                        "as": "thing"
7774                    },
7775                    {
7776                        "protocol": "logger",
7777                        "from": "parent",
7778                        "to": [ "#scenic" ],
7779                    },
7780                    {
7781                        "directory": "thing",
7782                        "from": "parent",
7783                        "to": [ "#echo_server" ],
7784                    }
7785                ],
7786                "children": [
7787                    {
7788                        "name": "scenic",
7789                        "url": "fuchsia-pkg://fuchsia.com/scenic/stable#meta/scenic.cm"
7790                    },
7791                    {
7792                        "name": "echo_server",
7793                        "url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo_server.cm"
7794                    },
7795                ],
7796            }),
7797            Err(Error::Validate { err, .. }) if &err == "\"thing\" is a duplicate \"offer\" target capability for \"#echo_server\""
7798        ),
7799        test_cml_offer_duplicate_storage_names(
7800            json!({
7801                "offer": [
7802                    {
7803                        "storage": "cache",
7804                        "from": "parent",
7805                        "to": [ "#echo_server" ]
7806                    },
7807                    {
7808                        "storage": "cache",
7809                        "from": "self",
7810                        "to": [ "#echo_server" ]
7811                    }
7812                ],
7813                "capabilities": [ {
7814                    "storage": "cache",
7815                    "from": "self",
7816                    "backing_dir": "minfs",
7817                    "storage_id": "static_instance_id_or_moniker",
7818                } ],
7819                "children": [ {
7820                    "name": "echo_server",
7821                    "url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo_server.cm"
7822                } ]
7823            }),
7824            Err(Error::Validate { err, .. }) if &err == "\"cache\" is a duplicate \"offer\" target capability for \"#echo_server\""
7825        ),
7826        // if "as" is specified, only 1 array item is allowed.
7827        test_cml_offer_bad_as(
7828            json!({
7829                "offer": [
7830                    {
7831                        "protocol": ["A", "B"],
7832                        "from": "parent",
7833                        "to": [ "#echo_server" ],
7834                        "as": "thing"
7835                    },
7836                ],
7837                "children": [
7838                    {
7839                        "name": "echo_server",
7840                        "url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo_server.cm"
7841                    }
7842                ]
7843            }),
7844            Err(Error::Validate { err, .. }) if &err == "\"as\" can only be specified when one `protocol` is supplied."
7845        ),
7846        test_cml_offer_bad_subdir_no_span(
7847            json!({
7848                "offer": [
7849                    {
7850                        "directory": "index",
7851                        "subdir": "/",
7852                        "from": "parent",
7853                        "to": [ "#modular" ],
7854                    },
7855                ],
7856                "children": [
7857                    {
7858                        "name": "modular",
7859                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
7860                    }
7861                ]
7862            }),
7863            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"/\", expected a path with no leading `/` and non-empty segments"
7864        ),
7865        test_cml_offer_from_self_no_span(
7866            json!({
7867                "offer": [
7868                    {
7869                        "protocol": "foo_protocol",
7870                        "from": "self",
7871                        "to": [ "#modular" ],
7872                    },
7873                    {
7874                        "protocol": [ "bar_protocol", "baz_protocol" ],
7875                        "from": "self",
7876                        "to": [ "#modular" ],
7877                    },
7878                    {
7879                        "directory": "foo_directory",
7880                        "from": "self",
7881                        "to": [ "#modular" ],
7882                    },
7883                    {
7884                        "runner": "foo_runner",
7885                        "from": "self",
7886                        "to": [ "#modular" ],
7887                    },
7888                    {
7889                        "resolver": "foo_resolver",
7890                        "from": "self",
7891                        "to": [ "#modular" ],
7892                    },
7893                ],
7894                "children": [
7895                    {
7896                        "name": "modular",
7897                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
7898                    },
7899                ],
7900                "capabilities": [
7901                    {
7902                        "protocol": "foo_protocol",
7903                    },
7904                    {
7905                        "protocol": "bar_protocol",
7906                    },
7907                    {
7908                        "protocol": "baz_protocol",
7909                    },
7910                    {
7911                        "directory": "foo_directory",
7912                        "path": "/dir",
7913                        "rights": [ "r*" ],
7914                    },
7915                    {
7916                        "runner": "foo_runner",
7917                        "path": "/svc/fuchsia.sys2.ComponentRunner",
7918                    },
7919                    {
7920                        "resolver": "foo_resolver",
7921                        "path": "/svc/fuchsia.component.resolution.Resolver",
7922                    },
7923                ]
7924            }),
7925            Ok(())
7926        ),
7927        test_cml_offer_service_from_self_missing_no_span(
7928            json!({
7929                "offer": [
7930                    {
7931                        "service": "pkg_service",
7932                        "from": "self",
7933                        "to": [ "#modular" ],
7934                    },
7935                ],
7936                "children": [
7937                    {
7938                        "name": "modular",
7939                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
7940                    },
7941                ],
7942            }),
7943            Err(Error::Validate { err, .. }) if &err == "service \"pkg_service\" is offered from self, so it must be declared as a \"service\" in \"capabilities\""
7944        ),
7945        test_cml_offer_protocol_from_self_missing_no_span(
7946            json!({
7947                "offer": [
7948                    {
7949                        "protocol": "pkg_protocol",
7950                        "from": "self",
7951                        "to": [ "#modular" ],
7952                    },
7953                ],
7954                "children": [
7955                    {
7956                        "name": "modular",
7957                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
7958                    },
7959                ],
7960            }),
7961            Err(Error::Validate { err, .. }) if &err == "protocol \"pkg_protocol\" is offered from self, so it must be declared as a \"protocol\" in \"capabilities\""
7962        ),
7963        test_cml_offer_protocol_from_self_missing_multiple_no_span(
7964            json!({
7965                "offer": [
7966                    {
7967                        "protocol": [ "foo_protocol", "bar_protocol" ],
7968                        "from": "self",
7969                        "to": [ "#modular" ],
7970                    },
7971                ],
7972                "children": [
7973                    {
7974                        "name": "modular",
7975                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
7976                    },
7977                ],
7978            }),
7979            Err(Error::Validate { err, .. }) if &err == "protocol \"foo_protocol\" is offered from self, so it must be declared as a \"protocol\" in \"capabilities\""
7980        ),
7981        test_cml_offer_directory_from_self_missing_no_span(
7982            json!({
7983                "offer": [
7984                    {
7985                        "directory": "pkg_directory",
7986                        "from": "self",
7987                        "to": [ "#modular" ],
7988                    },
7989                ],
7990                "children": [
7991                    {
7992                        "name": "modular",
7993                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
7994                    },
7995                ],
7996            }),
7997            Err(Error::Validate { err, .. }) if &err == "directory \"pkg_directory\" is offered from self, so it must be declared as a \"directory\" in \"capabilities\""
7998        ),
7999        test_cml_offer_runner_from_self_missing_no_span(
8000            json!({
8001                "offer": [
8002                    {
8003                        "runner": "dart",
8004                        "from": "self",
8005                        "to": [ "#modular" ],
8006                    },
8007                ],
8008                "children": [
8009                    {
8010                        "name": "modular",
8011                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
8012                    },
8013                ],
8014            }),
8015            Err(Error::Validate { err, .. }) if &err == "runner \"dart\" is offered from self, so it must be declared as a \"runner\" in \"capabilities\""
8016        ),
8017        test_cml_offer_resolver_from_self_missing_no_span(
8018            json!({
8019                "offer": [
8020                    {
8021                        "resolver": "pkg_resolver",
8022                        "from": "self",
8023                        "to": [ "#modular" ],
8024                    },
8025                ],
8026                "children": [
8027                    {
8028                        "name": "modular",
8029                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
8030                    },
8031                ],
8032            }),
8033            Err(Error::Validate { err, .. }) if &err == "resolver \"pkg_resolver\" is offered from self, so it must be declared as a \"resolver\" in \"capabilities\""
8034        ),
8035        test_cml_offer_storage_from_self_missing_no_span(
8036            json!({
8037                    "offer": [
8038                        {
8039                            "storage": "cache",
8040                            "from": "self",
8041                            "to": [ "#echo_server" ],
8042                        },
8043                    ],
8044                    "children": [
8045                        {
8046                            "name": "echo_server",
8047                            "url": "fuchsia-pkg://fuchsia.com/echo_server#meta/echo_server.cm",
8048                        },
8049                    ],
8050                }),
8051            Err(Error::Validate { err, .. }) if &err == "storage \"cache\" is offered from self, so it must be declared as a \"storage\" in \"capabilities\""
8052        ),
8053        test_cml_offer_from_self_missing_dictionary_no_span(
8054            json!({
8055                "offer": [
8056                    {
8057                        "protocol": "foo_protocol",
8058                        "from": "self/dict/inner",
8059                        "to": [ "#modular" ],
8060                    },
8061                ],
8062                "children": [
8063                    {
8064                        "name": "modular",
8065                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
8066                    },
8067                ],
8068            }),
8069            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\""
8070        ),
8071        test_cml_offer_dependency_on_wrong_type(
8072            json!({
8073                    "offer": [ {
8074                        "resolver": "fuchsia.logger.Log",
8075                        "from": "parent",
8076                        "to": [ "#echo_server" ],
8077                        "dependency": "strong",
8078                    } ],
8079                    "children": [ {
8080                        "name": "echo_server",
8081                        "url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo_server.cm",
8082                    } ],
8083                }),
8084            Err(Error::Validate { err, .. }) if err.starts_with("Dependency can only be provided for")
8085        ),
8086
8087        // children
8088        test_cml_children(
8089            json!({
8090                "children": [
8091                    {
8092                        "name": "logger",
8093                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
8094                        "on_terminate": "reboot",
8095                    },
8096                    {
8097                        "name": "gmail",
8098                        "url": "https://www.google.com/gmail",
8099                        "startup": "eager",
8100                    },
8101                ]
8102            }),
8103            Ok(())
8104        ),
8105        test_cml_children_missing_props(
8106            json!({
8107                "children": [ {} ]
8108            }),
8109            Err(Error::Parse { err, .. }) if &err == "missing field `name`"
8110        ),
8111        test_cml_children_duplicate_names(
8112            json!({
8113                "children": [
8114                     {
8115                         "name": "logger",
8116                         "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
8117                     },
8118                     {
8119                         "name": "logger",
8120                         "url": "fuchsia-pkg://fuchsia.com/logger/beta#meta/logger.cm"
8121                     }
8122                 ]
8123             }),
8124             Err(Error::Validate { err, .. }) if &err == "identifier \"logger\" is defined twice, once in \"children\" and once in \"children\""
8125         ),
8126         test_cml_children_url_ends_in_cml_no_span(
8127            json!({
8128                "children": [
8129                     {
8130                         "name": "logger",
8131                         "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cml"
8132                     },
8133                 ]
8134             }),
8135             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"
8136         ),
8137          test_cml_children_bad_startup(
8138            json!({
8139                "children": [
8140                    {
8141                        "name": "logger",
8142                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
8143                        "startup": "zzz",
8144                    },
8145                ],
8146            }),
8147            Err(Error::Parse { err, .. }) if &err == "unknown variant `zzz`, expected `lazy` or `eager`"
8148        ),
8149        test_cml_children_bad_on_terminate(
8150            json!({
8151                "children": [
8152                    {
8153                        "name": "logger",
8154                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
8155                        "on_terminate": "zzz",
8156                    },
8157                ],
8158            }),
8159            Err(Error::Parse { err, .. }) if &err == "unknown variant `zzz`, expected `none` or `reboot`"
8160        ),
8161
8162
8163        test_cml_children_bad_environment_no_span(
8164            json!({
8165                "children": [
8166                    {
8167                        "name": "logger",
8168                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
8169                        "environment": "parent",
8170                    }
8171                ]
8172            }),
8173            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"parent\", expected \"#<environment-name>\""
8174        ),
8175        test_cml_children_environment_no_span(
8176            json!({
8177                "children": [
8178                    {
8179                        "name": "logger",
8180                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
8181                        "environment": "#foo_env",
8182                    }
8183                ],
8184                "environments": [
8185                    {
8186                        "name": "foo_env",
8187                    }
8188                ]
8189            }),
8190            Ok(())
8191        ),
8192        test_cml_collections_bad_environment_no_span(
8193            json!({
8194                "collections": [
8195                    {
8196                        "name": "tests",
8197                        "durability": "transient",
8198                        "environment": "parent",
8199                    }
8200                ]
8201            }),
8202            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"parent\", expected \"#<environment-name>\""
8203        ),
8204        test_cml_collections_environment_no_span(
8205            json!({
8206                "collections": [
8207                    {
8208                        "name": "tests",
8209                        "durability": "transient",
8210                        "environment": "#foo_env",
8211                    }
8212                ],
8213                "environments": [
8214                    {
8215                        "name": "foo_env",
8216                    }
8217                ]
8218            }),
8219            Ok(())
8220        ),
8221
8222        test_cml_environment_timeout_no_span(
8223            json!({
8224                "environments": [
8225                    {
8226                        "name": "foo_env",
8227                        "__stop_timeout_ms": 10000,
8228                    }
8229                ]
8230            }),
8231            Ok(())
8232        ),
8233
8234        test_cml_environment_bad_timeout_no_span(
8235            json!({
8236                "environments": [
8237                    {
8238                        "name": "foo_env",
8239                        "__stop_timeout_ms": -3,
8240                    }
8241                ]
8242            }),
8243            Err(Error::Parse { err, .. }) if &err == "invalid value: integer `-3`, expected an unsigned 32-bit integer"
8244        ),
8245        test_cml_environment_debug_no_span(
8246            json!({
8247                "capabilities": [
8248                    {
8249                        "protocol": "fuchsia.logger.Log2",
8250                    },
8251                ],
8252                "environments": [
8253                    {
8254                        "name": "foo_env",
8255                        "extends": "realm",
8256                        "debug": [
8257                            {
8258                                "protocol": "fuchsia.module.Module",
8259                                "from": "#modular",
8260                            },
8261                            {
8262                                "protocol": "fuchsia.logger.OtherLog",
8263                                "from": "parent",
8264                            },
8265                            {
8266                                "protocol": "fuchsia.logger.Log2",
8267                                "from": "self",
8268                            },
8269                        ]
8270                    }
8271                ],
8272                "children": [
8273                    {
8274                        "name": "modular",
8275                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
8276                    },
8277                ],
8278            }),
8279           Ok(())
8280        ),
8281        test_cml_environment_debug_missing_capability_no_span(
8282            json!({
8283                "environments": [
8284                    {
8285                        "name": "foo_env",
8286                        "extends": "realm",
8287                        "debug": [
8288                            {
8289                                "protocol": "fuchsia.module.Module",
8290                                "from": "#modular",
8291                            },
8292                            {
8293                                "protocol": "fuchsia.logger.OtherLog",
8294                                "from": "parent",
8295                            },
8296                            {
8297                                "protocol": "fuchsia.logger.Log2",
8298                                "from": "self",
8299                            },
8300                        ]
8301                    }
8302                ],
8303                "children": [
8304                    {
8305                        "name": "modular",
8306                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
8307                    },
8308                ],
8309            }),
8310            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\""
8311        ),
8312        test_cml_environment_invalid_from_child_no_span(
8313            json!({
8314                "capabilities": [
8315                    {
8316                        "protocol": "fuchsia.logger.Log2",
8317                    },
8318                ],
8319                "environments": [
8320                    {
8321                        "name": "foo_env",
8322                        "extends": "realm",
8323                        "debug": [
8324                            {
8325                                "protocol": "fuchsia.module.Module",
8326                                "from": "#missing",
8327                            },
8328                            {
8329                                "protocol": "fuchsia.logger.OtherLog",
8330                                "from": "parent",
8331                            },
8332                            {
8333                                "protocol": "fuchsia.logger.Log2",
8334                                "from": "self",
8335                            },
8336                        ]
8337                    }
8338                ],
8339                "children": [
8340                    {
8341                        "name": "modular",
8342                        "url": "fuchsia-pkg://fuchsia.com/modular#meta/modular.cm"
8343                    },
8344                ],
8345            }),
8346            Err(Error::Validate { err, .. }) if &err == "\"debug\" source \"#missing\" does not appear in \"children\" or \"capabilities\""
8347        ),
8348
8349
8350        // collections
8351        test_cml_collections(
8352            json!({
8353                "collections": [
8354                    {
8355                        "name": "test_single_run_coll",
8356                        "durability": "single_run"
8357                    },
8358                    {
8359                        "name": "test_transient_coll",
8360                        "durability": "transient"
8361                    },
8362                ]
8363            }),
8364            Ok(())
8365        ),
8366        test_cml_collections_missing_props(
8367            json!({
8368                "collections": [ {} ]
8369            }),
8370            Err(Error::Parse { err, .. }) if &err == "missing field `name`"
8371        ),
8372        test_cml_collections_duplicate_names(
8373           json!({
8374               "collections": [
8375                    {
8376                        "name": "duplicate",
8377                        "durability": "single_run"
8378                    },
8379                    {
8380                        "name": "duplicate",
8381                        "durability": "transient"
8382                    }
8383                ]
8384            }),
8385            Err(Error::Validate { err, .. }) if &err == "identifier \"duplicate\" is defined twice, once in \"collections\" and once in \"collections\""
8386        ),
8387        test_cml_collections_bad_durability(
8388            json!({
8389                "collections": [
8390                    {
8391                        "name": "modular",
8392                        "durability": "zzz",
8393                    },
8394                ],
8395            }),
8396            Err(Error::Parse { err, .. }) if &err == "unknown variant `zzz`, expected `transient` or `single_run`"
8397        ),
8398
8399        // capabilities
8400        test_cml_protocol(
8401            json!({
8402                "capabilities": [
8403                    {
8404                        "protocol": "a",
8405                        "path": "/minfs",
8406                    },
8407                    {
8408                        "protocol": "b",
8409                        "path": "/data",
8410                    },
8411                    {
8412                        "protocol": "c",
8413                    },
8414                ],
8415            }),
8416            Ok(())
8417        ),
8418        test_cml_protocol_multi(
8419            json!({
8420                "capabilities": [
8421                    {
8422                        "protocol": ["a", "b", "c"],
8423                    },
8424                ],
8425            }),
8426            Ok(())
8427        ),
8428        test_cml_protocol_multi_invalid_path_no_span(
8429            json!({
8430                "capabilities": [
8431                    {
8432                        "protocol": ["a", "b", "c"],
8433                        "path": "/minfs",
8434                    },
8435                ],
8436            }),
8437            Err(Error::Validate { err, .. }) if &err == "\"path\" can only be specified when one `protocol` is supplied."
8438        ),
8439        test_cml_protocol_all_valid_chars(
8440            json!({
8441                "capabilities": [
8442                    {
8443                        "protocol": "abcdefghijklmnopqrstuvwxyz0123456789_-service",
8444                    },
8445                ],
8446            }),
8447            Ok(())
8448        ),
8449        test_cml_directory(
8450            json!({
8451                "capabilities": [
8452                    {
8453                        "directory": "a",
8454                        "path": "/minfs",
8455                        "rights": ["connect"],
8456                    },
8457                    {
8458                        "directory": "b",
8459                        "path": "/data",
8460                        "rights": ["connect"],
8461                    },
8462                ],
8463            }),
8464            Ok(())
8465        ),
8466        test_cml_directory_all_valid_chars(
8467            json!({
8468                "capabilities": [
8469                    {
8470                        "directory": "abcdefghijklmnopqrstuvwxyz0123456789_-service",
8471                        "path": "/data",
8472                        "rights": ["connect"],
8473                    },
8474                ],
8475            }),
8476            Ok(())
8477        ),
8478        test_cml_directory_missing_path_no_span(
8479            json!({
8480                "capabilities": [
8481                    {
8482                        "directory": "dir",
8483                        "rights": ["connect"],
8484                    },
8485                ]
8486            }),
8487            Err(Error::Validate { err, .. }) if &err == "\"path\" should be present with \"directory\""
8488        ),
8489        test_cml_directory_missing_rights_no_span(
8490            json!({
8491                "capabilities": [
8492                    {
8493                        "directory": "dir",
8494                        "path": "/dir",
8495                    },
8496                ]
8497            }),
8498            Err(Error::Validate { err, .. }) if &err == "\"rights\" should be present with \"directory\""
8499        ),
8500        test_cml_storage(
8501            json!({
8502                "capabilities": [
8503                    {
8504                        "storage": "a",
8505                        "from": "#minfs",
8506                        "backing_dir": "minfs",
8507                        "storage_id": "static_instance_id",
8508                    },
8509                    {
8510                        "storage": "b",
8511                        "from": "parent",
8512                        "backing_dir": "data",
8513                        "storage_id": "static_instance_id_or_moniker",
8514                    },
8515                    {
8516                        "storage": "c",
8517                        "from": "self",
8518                        "backing_dir": "storage",
8519                        "storage_id": "static_instance_id_or_moniker",
8520                    },
8521                ],
8522                "children": [
8523                    {
8524                        "name": "minfs",
8525                        "url": "fuchsia-pkg://fuchsia.com/minfs/stable#meta/minfs.cm",
8526                    },
8527                ],
8528            }),
8529            Ok(())
8530        ),
8531        test_cml_storage_all_valid_chars(
8532            json!({
8533                "capabilities": [
8534                    {
8535                        "storage": "abcdefghijklmnopqrstuvwxyz0123456789_-storage",
8536                        "from": "#abcdefghijklmnopqrstuvwxyz0123456789_-from",
8537                        "backing_dir": "example",
8538                        "storage_id": "static_instance_id_or_moniker",
8539                    },
8540                ],
8541                "children": [
8542                    {
8543                        "name": "abcdefghijklmnopqrstuvwxyz0123456789_-from",
8544                        "url": "https://www.google.com/gmail",
8545                    },
8546                ],
8547            }),
8548            Ok(())
8549        ),
8550        test_cml_storage_invalid_from(
8551            json!({
8552                    "capabilities": [ {
8553                        "storage": "minfs",
8554                        "from": "#missing",
8555                        "backing_dir": "minfs",
8556                        "storage_id": "static_instance_id_or_moniker",
8557                    } ]
8558                }),
8559            Err(Error::Validate { err, .. }) if &err == "\"capabilities\" source \"#missing\" does not appear in \"children\""
8560        ),
8561        test_cml_storage_missing_path_or_backing_dir_no_span(
8562            json!({
8563                    "capabilities": [ {
8564                        "storage": "minfs",
8565                        "from": "self",
8566                        "storage_id": "static_instance_id_or_moniker",
8567                    } ]
8568                }),
8569            Err(Error::Validate { err, .. }) if &err == "\"backing_dir\" should be present with \"storage\""
8570
8571        ),
8572        test_cml_storage_missing_storage_id_no_span(
8573            json!({
8574                    "capabilities": [ {
8575                        "storage": "minfs",
8576                        "from": "self",
8577                        "backing_dir": "storage",
8578                    }, ]
8579                }),
8580            Err(Error::Validate { err, .. }) if &err == "\"storage_id\" should be present with \"storage\""
8581        ),
8582        test_cml_storage_path_no_span(
8583            json!({
8584                    "capabilities": [ {
8585                        "storage": "minfs",
8586                        "from": "self",
8587                        "path": "/minfs",
8588                        "storage_id": "static_instance_id_or_moniker",
8589                    } ]
8590                }),
8591            Err(Error::Validate { err, .. }) if &err == "\"path\" cannot be present with \"storage\", use \"backing_dir\""
8592        ),
8593        test_cml_runner(
8594            json!({
8595                "capabilities": [
8596                    {
8597                        "runner": "a",
8598                        "path": "/minfs",
8599                    },
8600                ],
8601            }),
8602            Ok(())
8603        ),
8604        test_cml_runner_all_valid_chars(
8605            json!({
8606                "children": [
8607                    {
8608                        "name": "abcdefghijklmnopqrstuvwxyz0123456789_-from",
8609                        "url": "https://www.google.com/gmail"
8610                    },
8611                ],
8612                "capabilities": [
8613                    {
8614                        "runner": "abcdefghijklmnopqrstuvwxyz0123456789_-runner",
8615                        "path": "/example",
8616                    },
8617                ]
8618            }),
8619            Ok(())
8620        ),
8621        test_cml_runner_extraneous_from_no_span(
8622            json!({
8623                "capabilities": [
8624                    {
8625                        "runner": "a",
8626                        "path": "/example",
8627                        "from": "self",
8628                    },
8629                ]
8630            }),
8631            Err(Error::Validate { err, .. }) if &err == "\"from\" should not be present with \"runner\""
8632        ),
8633        test_cml_capability_missing_name_no_span(
8634            json!({
8635                "capabilities": [
8636                    {
8637                        "path": "/svc/fuchsia.component.resolution.Resolver",
8638                    },
8639                ]
8640            }),
8641            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\""
8642        ),
8643        test_cml_resolver_missing_path_no_span(
8644            json!({
8645                "capabilities": [
8646                    {
8647                        "resolver": "pkg_resolver",
8648                    },
8649                ]
8650            }),
8651            Err(Error::Validate { err, .. }) if &err == "\"path\" should be present with \"resolver\""
8652        ),
8653        test_cml_capabilities_extraneous_from_no_span(
8654            json!({
8655                "capabilities": [
8656                    {
8657                        "resolver": "pkg_resolver",
8658                        "path": "/svc/fuchsia.component.resolution.Resolver",
8659                        "from": "self",
8660                    },
8661                ]
8662            }),
8663            Err(Error::Validate { err, .. }) if &err == "\"from\" should not be present with \"resolver\""
8664        ),
8665        test_cml_capabilities_duplicates(
8666            json!({
8667                "capabilities": [
8668                    {
8669                        "runner": "pkg_resolver",
8670                        "path": "/svc/fuchsia.component.resolution.Resolver",
8671                    },
8672                    {
8673                        "resolver": "pkg_resolver",
8674                        "path": "/svc/my-resolver",
8675                    },
8676                ]
8677            }),
8678            Err(Error::Validate { err, .. }) if &err == "identifier \"pkg_resolver\" is defined twice, once in \"resolvers\" and once in \"runners\""
8679        ),
8680
8681        // environments
8682        test_cml_environments(
8683            json!({
8684                "environments": [
8685                    {
8686                        "name": "my_env_a",
8687                    },
8688                    {
8689                        "name": "my_env_b",
8690                        "extends": "realm",
8691                    },
8692                    {
8693                        "name": "my_env_c",
8694                        "extends": "none",
8695                        "__stop_timeout_ms": 8000,
8696                    },
8697                ],
8698            }),
8699            Ok(())
8700        ),
8701
8702        test_invalid_cml_environment_no_stop_timeout(
8703            json!({
8704                "environments": [
8705                    {
8706                        "name": "my_env",
8707                        "extends": "none",
8708                    },
8709                ],
8710            }),
8711            Err(Error::Validate { err, .. }) if &err ==
8712                "'__stop_timeout_ms' must be provided if the environment does not extend \
8713                another environment"
8714        ),
8715
8716        test_cml_environment_invalid_extends(
8717            json!({
8718                "environments": [
8719                    {
8720                        "name": "my_env",
8721                        "extends": "some_made_up_string",
8722                    },
8723                ],
8724            }),
8725            Err(Error::Parse { err, .. }) if &err == "unknown variant `some_made_up_string`, expected `realm` or `none`"
8726        ),
8727        test_cml_environment_missing_props(
8728            json!({
8729                "environments": [ {} ]
8730            }),
8731            Err(Error::Parse { err, .. }) if &err == "missing field `name`"
8732        ),
8733
8734        test_cml_environment_with_runners(
8735            json!({
8736                "environments": [
8737                    {
8738                        "name": "my_env",
8739                        "extends": "realm",
8740                        "runners": [
8741                            {
8742                                "runner": "dart",
8743                                "from": "parent",
8744                            }
8745                        ]
8746                    }
8747                ],
8748            }),
8749            Ok(())
8750        ),
8751        test_cml_environment_with_runners_alias(
8752            json!({
8753                "environments": [
8754                    {
8755                        "name": "my_env",
8756                        "extends": "realm",
8757                        "runners": [
8758                            {
8759                                "runner": "dart",
8760                                "from": "parent",
8761                                "as": "my-dart",
8762                            }
8763                        ]
8764                    }
8765                ],
8766            }),
8767            Ok(())
8768        ),
8769        test_cml_environment_with_runners_missing(
8770            json!({
8771                "environments": [
8772                    {
8773                        "name": "my_env",
8774                        "extends": "realm",
8775                        "runners": [
8776                            {
8777                                "runner": "dart",
8778                                "from": "self",
8779                            }
8780                        ]
8781                    }
8782                ],
8783                "capabilities": [
8784                     {
8785                         "runner": "dart",
8786                         "path": "/svc/fuchsia.component.Runner",
8787                     }
8788                ],
8789            }),
8790            Ok(())
8791        ),
8792        test_cml_environment_with_runners_bad_name(
8793            json!({
8794                "environments": [
8795                    {
8796                        "name": "my_env",
8797                        "extends": "realm",
8798                        "runners": [
8799                            {
8800                                "runner": "elf",
8801                                "from": "parent",
8802                                "as": "#elf",
8803                            }
8804                        ]
8805                    }
8806                ],
8807            }),
8808            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"#elf\", expected a \
8809            name that consists of [A-Za-z0-9_.-] and starts with [A-Za-z0-9_]"
8810        ),
8811        test_cml_environment_with_runners_duplicate_name(
8812            json!({
8813                "environments": [
8814                    {
8815                        "name": "my_env",
8816                        "extends": "realm",
8817                        "runners": [
8818                            {
8819                                "runner": "dart",
8820                                "from": "parent",
8821                            },
8822                            {
8823                                "runner": "other-dart",
8824                                "from": "parent",
8825                                "as": "dart",
8826                            }
8827                        ]
8828                    }
8829                ],
8830            }),
8831            Err(Error::Validate { err, .. }) if &err == "Duplicate runners registered under name \"dart\": \"other-dart\" and \"dart\"."
8832        ),
8833        test_cml_environment_with_runner_from_missing_child(
8834            json!({
8835                "environments": [
8836                    {
8837                        "name": "my_env",
8838                        "extends": "realm",
8839                        "runners": [
8840                            {
8841                                "runner": "elf",
8842                                "from": "#missing_child",
8843                            }
8844                        ]
8845                    }
8846                ]
8847            }),
8848            Err(Error::Validate { err, .. }) if &err == "\"elf\" runner source \"#missing_child\" does not appear in \"children\""
8849        ),
8850        test_cml_environment_with_resolvers(
8851            json!({
8852                "environments": [
8853                    {
8854                        "name": "my_env",
8855                        "extends": "realm",
8856                        "resolvers": [
8857                            {
8858                                "resolver": "pkg_resolver",
8859                                "from": "parent",
8860                                "scheme": "fuchsia-pkg",
8861                            }
8862                        ]
8863                    }
8864                ],
8865            }),
8866            Ok(())
8867        ),
8868        test_cml_environment_with_resolvers_bad_scheme(
8869            json!({
8870                "environments": [
8871                    {
8872                        "name": "my_env",
8873                        "extends": "realm",
8874                        "resolvers": [
8875                            {
8876                                "resolver": "pkg_resolver",
8877                                "from": "parent",
8878                                "scheme": "9scheme",
8879                            }
8880                        ]
8881                    }
8882                ],
8883            }),
8884            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"9scheme\", expected a valid URL scheme"
8885        ),
8886        test_cml_environment_with_resolvers_duplicate_scheme(
8887            json!({
8888                "environments": [
8889                    {
8890                        "name": "my_env",
8891                        "extends": "realm",
8892                        "resolvers": [
8893                            {
8894                                "resolver": "pkg_resolver",
8895                                "from": "parent",
8896                                "scheme": "fuchsia-pkg",
8897                            },
8898                            {
8899                                "resolver": "base_resolver",
8900                                "from": "parent",
8901                                "scheme": "fuchsia-pkg",
8902                            }
8903                        ]
8904                    }
8905                ],
8906            }),
8907            Err(Error::Validate { err, .. }) if &err == "scheme \"fuchsia-pkg\" for resolver \"base_resolver\" is already registered; previously registered to resolver \"pkg_resolver\"."
8908        ),
8909        test_cml_environment_with_resolver_from_missing_child(
8910            json!({
8911                "environments": [
8912                    {
8913                        "name": "my_env",
8914                        "extends": "realm",
8915                        "resolvers": [
8916                            {
8917                                "resolver": "pkg_resolver",
8918                                "from": "#missing_child",
8919                                "scheme": "fuchsia-pkg",
8920                            }
8921                        ]
8922                    }
8923                ]
8924            }),
8925            Err(Error::Validate { err, .. }) if &err == "\"pkg_resolver\" resolver source \"#missing_child\" does not appear in \"children\""
8926        ),
8927
8928        // facets
8929        test_cml_facets_no_span(
8930            json!({
8931                "facets": {
8932                    "metadata": {
8933                        "title": "foo",
8934                        "authors": [ "me", "you" ],
8935                        "year": 2018
8936                    }
8937                }
8938            }),
8939            Ok(())
8940        ),
8941        test_cml_facets_wrong_type_no_span(
8942            json!({
8943                "facets": 55
8944            }),
8945            Err(Error::Parse { err, .. }) if &err == "invalid type: integer `55`, expected a map"
8946        ),
8947
8948        // constraints
8949        test_cml_rights_all(
8950            json!({
8951                "use": [
8952                  {
8953                    "directory": "mydir",
8954                    "path": "/mydir",
8955                    "rights": ["connect", "enumerate", "read_bytes", "write_bytes",
8956                               "execute", "update_attributes", "get_attributes", "traverse",
8957                               "modify_directory"],
8958                  },
8959                ]
8960            }),
8961            Ok(())
8962        ),
8963        test_cml_rights_invalid(
8964            json!({
8965                "use": [
8966                  {
8967                    "directory": "mydir",
8968                    "path": "/mydir",
8969                    "rights": ["cAnnect", "enumerate"],
8970                  },
8971                ]
8972            }),
8973            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*`"
8974        ),
8975        test_cml_rights_duplicate(
8976            json!({
8977                "use": [
8978                  {
8979                    "directory": "mydir",
8980                    "path": "/mydir",
8981                    "rights": ["connect", "connect"],
8982                  },
8983                ]
8984            }),
8985            Err(Error::Parse { err, .. }) if &err == "invalid value: array with duplicate element, expected a nonempty array of rights, with unique elements"
8986        ),
8987        test_cml_rights_empty(
8988            json!({
8989                "use": [
8990                  {
8991                    "directory": "mydir",
8992                    "path": "/mydir",
8993                    "rights": [],
8994                  },
8995                ]
8996            }),
8997            Err(Error::Parse { err, .. }) if &err == "invalid length 0, expected a nonempty array of rights, with unique elements"
8998        ),
8999        test_cml_rights_alias_star_expansion(
9000            json!({
9001                "use": [
9002                  {
9003                    "directory": "mydir",
9004                    "rights": ["r*"],
9005                    "path": "/mydir",
9006                  },
9007                ]
9008            }),
9009            Ok(())
9010        ),
9011        test_cml_rights_alias_star_expansion_with_longform(
9012            json!({
9013                "use": [
9014                  {
9015                    "directory": "mydir",
9016                    "rights": ["w*", "read_bytes"],
9017                    "path": "/mydir",
9018                  },
9019                ]
9020            }),
9021            Ok(())
9022        ),
9023        test_cml_rights_alias_star_expansion_with_longform_collision_no_span(
9024            json!({
9025                "use": [
9026                  {
9027                    "directory": "mydir",
9028                    "path": "/mydir",
9029                    "rights": ["r*", "read_bytes"],
9030                  },
9031                ]
9032            }),
9033            Err(Error::Validate { err, .. }) if &err == "\"read_bytes\" is duplicated in the rights clause."
9034        ),
9035        test_cml_rights_alias_star_expansion_collision_no_span(
9036            json!({
9037                "use": [
9038                  {
9039                    "directory": "mydir",
9040                    "path": "/mydir",
9041                    "rights": ["w*", "x*"],
9042                  },
9043                ]
9044            }),
9045            Err(Error::Validate { err, .. }) if &err == "\"x*\" is duplicated in the rights clause."
9046        ),
9047        test_cml_rights_use_invalid_no_span(
9048            json!({
9049                "use": [
9050                  { "directory": "mydir", "path": "/mydir" },
9051                ]
9052            }),
9053            Err(Error::Validate { err, .. }) if &err == "This use statement requires a `rights` field. Refer to: https://fuchsia.dev/go/components/directory#consumer."
9054        ),
9055
9056        test_cml_path(
9057            json!({
9058                "capabilities": [
9059                    {
9060                        "protocol": "foo",
9061                        "path": "/foo/in.-_/Bar",
9062                    },
9063                ]
9064            }),
9065            Ok(())
9066        ),
9067        test_cml_path_invalid_empty(
9068            json!({
9069                "capabilities": [
9070                    { "protocol": "foo", "path": "" },
9071                ]
9072            }),
9073            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"
9074        ),
9075        test_cml_path_invalid_root(
9076            json!({
9077                "capabilities": [
9078                    { "protocol": "foo", "path": "/" },
9079                ]
9080            }),
9081            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"
9082        ),
9083        test_cml_path_invalid_absolute_is_relative(
9084            json!({
9085                "capabilities": [
9086                    { "protocol": "foo", "path": "foo/bar" },
9087                ]
9088            }),
9089            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"
9090        ),
9091        test_cml_path_invalid_trailing(
9092            json!({
9093                "capabilities": [
9094                    { "protocol": "foo", "path":"/foo/bar/" },
9095                ]
9096            }),
9097            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"
9098        ),
9099        test_cml_path_too_long(
9100            json!({
9101                "capabilities": [
9102                    { "protocol": "foo", "path": format!("/{}", "a".repeat(4095)) },
9103                ]
9104            }),
9105            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"
9106        ),
9107        test_cml_path_invalid_segment(
9108            json!({
9109                "capabilities": [
9110                    { "protocol": "foo", "path": "/foo/../bar" },
9111                ]
9112            }),
9113            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"
9114        ),
9115        test_cml_relative_path(
9116            json!({
9117                "use": [
9118                    {
9119                        "directory": "foo",
9120                        "path": "/foo",
9121                        "rights": ["r*"],
9122                        "subdir": "Baz/Bar",
9123                    },
9124                ]
9125            }),
9126            Ok(())
9127        ),
9128        test_cml_relative_path_invalid_empty(
9129            json!({
9130                "use": [
9131                    {
9132                        "directory": "foo",
9133                        "path": "/foo",
9134                        "rights": ["r*"],
9135                        "subdir": "",
9136                    },
9137                ]
9138            }),
9139            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"
9140        ),
9141        test_cml_relative_path_invalid_root(
9142            json!({
9143                "use": [
9144                    {
9145                        "directory": "foo",
9146                        "path": "/foo",
9147                        "rights": ["r*"],
9148                        "subdir": "/",
9149                    },
9150                ]
9151            }),
9152            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"/\", expected a path with no leading `/` and non-empty segments"
9153        ),
9154        test_cml_relative_path_invalid_absolute(
9155            json!({
9156                "use": [
9157                    {
9158                        "directory": "foo",
9159                        "path": "/foo",
9160                        "rights": ["r*"],
9161                        "subdir": "/bar",
9162                    },
9163                ]
9164            }),
9165            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"/bar\", expected a path with no leading `/` and non-empty segments"
9166        ),
9167        test_cml_relative_path_invalid_trailing(
9168            json!({
9169                "use": [
9170                    {
9171                        "directory": "foo",
9172                        "path": "/foo",
9173                        "rights": ["r*"],
9174                        "subdir": "bar/",
9175                    },
9176                ]
9177            }),
9178            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"bar/\", expected a path with no leading `/` and non-empty segments"
9179        ),
9180        test_cml_relative_path_too_long(
9181            json!({
9182                "use": [
9183                    {
9184                        "directory": "foo",
9185                        "path": "/foo",
9186                        "rights": ["r*"],
9187                        "subdir": format!("{}", "a".repeat(4096)),
9188                    },
9189                ]
9190            }),
9191            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"
9192        ),
9193        test_cml_relative_ref_too_long(
9194            json!({
9195                "expose": [
9196                    {
9197                        "protocol": "fuchsia.logger.Log",
9198                        "from": &format!("#{}", "a".repeat(256)),
9199                    },
9200                ],
9201                "children": [
9202                    {
9203                        "name": "logger",
9204                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
9205                    },
9206                ]
9207            }),
9208            Err(Error::Parse { err, .. }) if &err == "invalid length 257, expected one or an array of \"framework\", \"self\", \"#<child-name>\", or a dictionary path"
9209        ),
9210        test_cml_dictionary_ref_invalid_root(
9211            json!({
9212                "use": [
9213                    {
9214                        "protocol": "a",
9215                        "from": "bad/a",
9216                    },
9217                ],
9218            }),
9219            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"
9220        ),
9221        test_cml_dictionary_ref_invalid_path(
9222            json!({
9223                "use": [
9224                    {
9225                        "protocol": "a",
9226                        "from": "parent//a",
9227                    },
9228                ],
9229            }),
9230            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"
9231        ),
9232        test_cml_dictionary_ref_too_long(
9233            json!({
9234                "use": [
9235                    {
9236                        "protocol": "a",
9237                        "from": format!("parent/{}", "a".repeat(4089)),
9238                    },
9239                ],
9240            }),
9241            Err(Error::Parse { err, .. }) if &err == "invalid length 4096, expected \"parent\", \"framework\", \"debug\", \"self\", \"#<capability-name>\", \"#<child-name>\", \"#<collection-name>\", dictionary path, or none"
9242        ),
9243        test_cml_capability_name(
9244            json!({
9245                "use": [
9246                    {
9247                        "protocol": "abcdefghijklmnopqrstuvwxyz0123456789_-.",
9248                    },
9249                ]
9250            }),
9251            Ok(())
9252        ),
9253        test_cml_capability_name_invalid(
9254            json!({
9255                "use": [
9256                    {
9257                        "protocol": "/bad",
9258                    },
9259                ]
9260            }),
9261            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"/bad\", expected a name or nonempty array of names, with unique elements"
9262        ),
9263        test_cml_child_name(
9264            json!({
9265                "children": [
9266                    {
9267                        "name": "abcdefghijklmnopqrstuvwxyz0123456789_-.",
9268                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
9269                    },
9270                ]
9271            }),
9272            Ok(())
9273        ),
9274        test_cml_child_name_invalid(
9275            json!({
9276                "children": [
9277                    {
9278                        "name": "/bad",
9279                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
9280                    },
9281                ]
9282            }),
9283            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"/bad\", expected a \
9284            name that consists of [A-Za-z0-9_.-] and starts with [A-Za-z0-9_]"
9285        ),
9286        test_cml_child_name_too_long(
9287            json!({
9288                "children": [
9289                    {
9290                        "name": "a".repeat(256),
9291                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
9292                    }
9293                ]
9294            }),
9295            Err(Error::Parse { err, .. }) if &err == "invalid length 256, expected a non-empty name no more than 255 characters in length"
9296        ),
9297        test_cml_url(
9298            json!({
9299                "children": [
9300                    {
9301                        "name": "logger",
9302                        "url": "my+awesome-scheme.2://abc123!@$%.com",
9303                    },
9304                ]
9305            }),
9306            Ok(())
9307        ),
9308        test_cml_url_host_pound_invalid(
9309            json!({
9310                "children": [
9311                    {
9312                        "name": "logger",
9313                        "url": "my+awesome-scheme.2://abc123!@#$%.com",
9314                    },
9315                ]
9316            }),
9317            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"my+awesome-scheme.2://abc123!@#$%.com\", expected a valid URL"
9318        ),
9319        test_cml_url_invalid(
9320            json!({
9321                "children": [
9322                    {
9323                        "name": "logger",
9324                        "url": "fuchsia-pkg",
9325                    },
9326                ]
9327            }),
9328            Err(Error::Parse { err, .. }) if &err == "invalid value: string \"fuchsia-pkg\", expected a valid URL"
9329        ),
9330        test_cml_url_too_long(
9331            json!({
9332                "children": [
9333                    {
9334                        "name": "logger",
9335                        "url": &format!("fuchsia-pkg://{}", "a".repeat(4083)),
9336                    },
9337                ]
9338            }),
9339            Err(Error::Parse { err, .. }) if &err == "invalid length 4097, expected a non-empty URL no more than 4096 characters in length"
9340        ),
9341        test_cml_duplicate_identifiers_children_collection(
9342           json!({
9343               "children": [
9344                    {
9345                        "name": "logger",
9346                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
9347                    }
9348               ],
9349               "collections": [
9350                   {
9351                       "name": "logger",
9352                       "durability": "transient"
9353                   }
9354               ]
9355           }),
9356           Err(Error::Validate { err, .. }) if &err == "identifier \"logger\" is defined twice, once in \"collections\" and once in \"children\""
9357        ),
9358        test_cml_duplicate_identifiers_children_storage(
9359           json!({
9360               "children": [
9361                    {
9362                        "name": "logger",
9363                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
9364                    }
9365               ],
9366               "capabilities": [
9367                    {
9368                        "storage": "logger",
9369                        "path": "/logs",
9370                        "from": "parent"
9371                    }
9372                ]
9373           }),
9374           Err(Error::Validate { err, .. }) if &err == "identifier \"logger\" is defined twice, once in \"storage\" and once in \"children\""
9375        ),
9376        test_cml_duplicate_identifiers_collection_storage(
9377           json!({
9378               "collections": [
9379                    {
9380                        "name": "logger",
9381                        "durability": "transient"
9382                    }
9383                ],
9384                "capabilities": [
9385                    {
9386                        "storage": "logger",
9387                        "path": "/logs",
9388                        "from": "parent"
9389                    }
9390                ]
9391           }),
9392           Err(Error::Validate { err, .. }) if &err == "identifier \"logger\" is defined twice, once in \"storage\" and once in \"collections\""
9393        ),
9394        test_cml_duplicate_identifiers_children_runners(
9395           json!({
9396               "children": [
9397                    {
9398                        "name": "logger",
9399                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
9400                    }
9401               ],
9402               "capabilities": [
9403                    {
9404                        "runner": "logger",
9405                        "from": "parent"
9406                    }
9407                ]
9408           }),
9409           Err(Error::Validate { err, .. }) if &err == "identifier \"logger\" is defined twice, once in \"runners\" and once in \"children\""
9410        ),
9411        test_cml_duplicate_identifiers_environments(
9412            json!({
9413                "children": [
9414                     {
9415                         "name": "logger",
9416                         "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
9417                     }
9418                ],
9419                "environments": [
9420                     {
9421                         "name": "logger",
9422                     }
9423                 ]
9424            }),
9425            Err(Error::Validate { err, .. }) if &err == "identifier \"logger\" is defined twice, once in \"environments\" and once in \"children\""
9426        ),
9427
9428        // deny unknown fields
9429        test_deny_unknown_fields(
9430            json!(
9431                {
9432                    "program": {
9433                        "runner": "elf",
9434                        "binary": "bin/app",
9435                    },
9436                    "unknown_field": {},
9437                }
9438            ),
9439            Err(Error::Parse { err, .. }) if err.starts_with("unknown field `unknown_field`, expected one of ")
9440        ),
9441        test_offer_source_availability_unknown(
9442            json!({
9443                "children": [
9444                    {
9445                        "name": "foo",
9446                        "url": "fuchsia-pkg://foo.com/foo#meta/foo.cm"
9447                    },
9448                ],
9449                "offer": [
9450                    {
9451                        "protocol": "fuchsia.examples.Echo",
9452                        "from": "#bar",
9453                        "to": "#foo",
9454                        "availability": "optional",
9455                        "source_availability": "unknown",
9456                    },
9457                ],
9458            }),
9459            Ok(())
9460        ),
9461        test_offer_source_availability_required(
9462            json!({
9463                "children": [
9464                    {
9465                        "name": "foo",
9466                        "url": "fuchsia-pkg://foo.com/foo#meta/foo.cm"
9467                    },
9468                ],
9469                "offer": [
9470                    {
9471                        "protocol": "fuchsia.examples.Echo",
9472                        "from": "#bar",
9473                        "to": "#foo",
9474                        "source_availability": "required",
9475                    },
9476                ],
9477            }),
9478            Err(Error::Validate { err, .. }) if &err == "\"offer\" source \"#bar\" does not appear in \"children\" or \"capabilities\""
9479        ),
9480        test_offer_source_availability_omitted(
9481            json!({
9482                "children": [
9483                    {
9484                        "name": "foo",
9485                        "url": "fuchsia-pkg://foo.com/foo#meta/foo.cm"
9486                    },
9487                ],
9488                "offer": [
9489                    {
9490                        "protocol": "fuchsia.examples.Echo",
9491                        "from": "#bar",
9492                        "to": "#foo",
9493                    },
9494                ],
9495            }),
9496            Err(Error::Validate { err, .. }) if &err == "\"offer\" source \"#bar\" does not appear in \"children\" or \"capabilities\""
9497        ),
9498        test_cml_use_invalid_availability_no_span(
9499            json!({
9500                "use": [
9501                    {
9502                        "protocol": "fuchsia.examples.Echo",
9503                        "availability": "same_as_target",
9504                    },
9505                ],
9506            }),
9507            Err(Error::Validate { err, .. }) if &err == "\"availability: same_as_target\" cannot be used with use declarations"
9508        ),
9509        test_offer_source_void_availability_required(
9510            json!({
9511                "children": [
9512                    {
9513                        "name": "foo",
9514                        "url": "fuchsia-pkg://foo.com/foo#meta/foo.cm"
9515                    },
9516                ],
9517                "offer": [
9518                    {
9519                        "protocol": "fuchsia.examples.Echo",
9520                        "from": "void",
9521                        "to": "#foo",
9522                        "availability": "required",
9523                    },
9524                ],
9525            }),
9526            Err(Error::Validate { err, .. }) if &err == "capabilities with a source of \"void\" must have an availability of \"optional\", capabilities: \"fuchsia.examples.Echo\", from: \"void\""
9527        ),
9528        test_offer_source_void_availability_same_as_target(
9529            json!({
9530                "children": [
9531                    {
9532                        "name": "foo",
9533                        "url": "fuchsia-pkg://foo.com/foo#meta/foo.cm"
9534                    },
9535                ],
9536                "offer": [
9537                    {
9538                        "protocol": "fuchsia.examples.Echo",
9539                        "from": "void",
9540                        "to": "#foo",
9541                        "availability": "same_as_target",
9542                    },
9543                ],
9544            }),
9545            Err(Error::Validate { err, .. }) if &err == "capabilities with a source of \"void\" must have an availability of \"optional\", capabilities: \"fuchsia.examples.Echo\", from: \"void\""
9546        ),
9547        test_offer_source_missing_availability_required(
9548            json!({
9549                "children": [
9550                    {
9551                        "name": "foo",
9552                        "url": "fuchsia-pkg://foo.com/foo#meta/foo.cm"
9553                    },
9554                ],
9555                "offer": [
9556                    {
9557                        "protocol": "fuchsia.examples.Echo",
9558                        "from": "#bar",
9559                        "to": "#foo",
9560                        "availability": "required",
9561                        "source_availability": "unknown",
9562                    },
9563                ],
9564            }),
9565            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\""
9566        ),
9567        test_offer_source_missing_availability_same_as_target(
9568            json!({
9569                "children": [
9570                    {
9571                        "name": "foo",
9572                        "url": "fuchsia-pkg://foo.com/foo#meta/foo.cm"
9573                    },
9574                ],
9575                "offer": [
9576                    {
9577                        "protocol": "fuchsia.examples.Echo",
9578                        "from": "#bar",
9579                        "to": "#foo",
9580                        "availability": "same_as_target",
9581                        "source_availability": "unknown",
9582                    },
9583                ],
9584            }),
9585            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\""
9586        ),
9587        test_expose_source_availability_unknown(
9588            json!({
9589                "expose": [
9590                    {
9591                        "protocol": "fuchsia.examples.Echo",
9592                        "from": "#bar",
9593                        "availability": "optional",
9594                        "source_availability": "unknown",
9595                    },
9596                ],
9597            }),
9598            Ok(())
9599        ),
9600        test_expose_source_availability_required(
9601            json!({
9602                "expose": [
9603                    {
9604                        "protocol": "fuchsia.examples.Echo",
9605                        "from": "#bar",
9606                        "source_availability": "required",
9607                    },
9608                ],
9609            }),
9610            Err(Error::Validate { err, .. }) if &err == "\"expose\" source \"#bar\" does not appear in \"children\" or \"capabilities\""
9611        ),
9612        test_expose_source_availability_omitted(
9613            json!({
9614                "expose": [
9615                    {
9616                        "protocol": "fuchsia.examples.Echo",
9617                        "from": "#bar",
9618                    },
9619                ],
9620            }),
9621            Err(Error::Validate { err, .. }) if &err == "\"expose\" source \"#bar\" does not appear in \"children\" or \"capabilities\""
9622        ),
9623        test_expose_source_void_availability_required(
9624            json!({
9625                "expose": [
9626                    {
9627                        "protocol": "fuchsia.examples.Echo",
9628                        "from": "void",
9629                        "availability": "required",
9630                    },
9631                ],
9632            }),
9633            Err(Error::Validate { err, .. }) if &err == "capabilities with a source of \"void\" must have an availability of \"optional\", capabilities: \"fuchsia.examples.Echo\", from: \"void\""
9634        ),
9635        test_expose_source_void_availability_same_as_target(
9636            json!({
9637                "expose": [
9638                    {
9639                        "protocol": "fuchsia.examples.Echo",
9640                        "from": "void",
9641                        "availability": "same_as_target",
9642                    },
9643                ],
9644            }),
9645            Err(Error::Validate { err, .. }) if &err == "capabilities with a source of \"void\" must have an availability of \"optional\", capabilities: \"fuchsia.examples.Echo\", from: \"void\""
9646        ),
9647        test_expose_source_missing_availability_required(
9648            json!({
9649                "expose": [
9650                    {
9651                        "protocol": "fuchsia.examples.Echo",
9652                        "from": "#bar",
9653                        "availability": "required",
9654                        "source_availability": "unknown",
9655                    },
9656                ],
9657            }),
9658            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\""
9659        ),
9660        test_expose_source_missing_availability_same_as_target(
9661            json!({
9662                "expose": [
9663                    {
9664                        "protocol": "fuchsia.examples.Echo",
9665                        "from": "#bar",
9666                        "availability": "same_as_target",
9667                        "source_availability": "unknown",
9668                    },
9669                ],
9670            }),
9671            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\""
9672        ),
9673    }
9674
9675    // Tests for services.
9676    test_validate_cml! {
9677        test_cml_validate_use_service(
9678            json!({
9679                "use": [
9680                    { "service": "CoolFonts", "path": "/svc/fuchsia.fonts.Provider" },
9681                    { "service": "fuchsia.component.Realm", "from": "framework" },
9682                ],
9683            }),
9684            Ok(())
9685        ),
9686        test_cml_use_invalid_from_with_service_no_span(
9687            json!({
9688                "use": [ { "service": "foo", "from": "debug" } ]
9689            }),
9690            Err(Error::Validate { err, .. }) if &err == "only \"protocol\" supports source from \"debug\""
9691        ),
9692        test_cml_validate_offer_service(
9693            json!({
9694                "offer": [
9695                    {
9696                        "service": "fuchsia.logger.Log",
9697                        "from": "#logger",
9698                        "to": [ "#echo_server", "#modular" ],
9699                        "as": "fuchsia.logger.SysLog"
9700                    },
9701                    {
9702                        "service": "fuchsia.fonts.Provider",
9703                        "from": "parent",
9704                        "to": [ "#echo_server" ]
9705                    },
9706                    {
9707                        "service": "fuchsia.net.Netstack",
9708                        "from": "self",
9709                        "to": [ "#echo_server" ]
9710                    },
9711                ],
9712                "children": [
9713                    {
9714                        "name": "logger",
9715                        "url": "fuchsia-pkg://logger",
9716                    },
9717                    {
9718                        "name": "echo_server",
9719                        "url": "fuchsia-pkg://echo_server",
9720                    }
9721                ],
9722                "collections": [
9723                    {
9724                        "name": "modular",
9725                        "durability": "transient",
9726                    },
9727                ],
9728                "capabilities": [
9729                    { "service": "fuchsia.net.Netstack" },
9730                ],
9731            }),
9732            Ok(())
9733        ),
9734        test_cml_validate_expose_service(
9735            json!(
9736                {
9737                    "expose": [
9738                        {
9739                            "service": "fuchsia.fonts.Provider",
9740                            "from": "self",
9741                        },
9742                        {
9743                            "service": "fuchsia.logger.Log",
9744                            "from": "#logger",
9745                            "as": "logger"
9746                        },
9747                    ],
9748                    "capabilities": [
9749                        { "service": "fuchsia.fonts.Provider" },
9750                    ],
9751                    "children": [
9752                        {
9753                            "name": "logger",
9754                            "url": "fuchsia-pkg://logger",
9755                        },
9756                    ]
9757                }
9758            ),
9759            Ok(())
9760        ),
9761        test_cml_validate_expose_service_multi_source(
9762            json!(
9763                {
9764                    "expose": [
9765                        {
9766                            "service": "fuchsia.my.Service",
9767                            "from": [ "self", "#a" ],
9768                        },
9769                        {
9770                            "service": "fuchsia.my.Service",
9771                            "from": "#coll",
9772                        },
9773                    ],
9774                    "capabilities": [
9775                        { "service": "fuchsia.my.Service" },
9776                    ],
9777                    "children": [
9778                        {
9779                            "name": "a",
9780                            "url": "fuchsia-pkg://a",
9781                        },
9782                    ],
9783                    "collections": [
9784                        {
9785                            "name": "coll",
9786                            "durability": "transient",
9787                        },
9788                    ],
9789                }
9790            ),
9791            Ok(())
9792        ),
9793        test_cml_validate_offer_service_multi_source(
9794            json!(
9795                {
9796                    "offer": [
9797                        {
9798                            "service": "fuchsia.my.Service",
9799                            "from": [ "self", "parent" ],
9800                            "to": "#b",
9801                        },
9802                        {
9803                            "service": "fuchsia.my.Service",
9804                            "from": [ "#a", "#coll" ],
9805                            "to": "#b",
9806                        },
9807                    ],
9808                    "capabilities": [
9809                        { "service": "fuchsia.my.Service" },
9810                    ],
9811                    "children": [
9812                        {
9813                            "name": "a",
9814                            "url": "fuchsia-pkg://a",
9815                        },
9816                        {
9817                            "name": "b",
9818                            "url": "fuchsia-pkg://b",
9819                        },
9820                    ],
9821                    "collections": [
9822                        {
9823                            "name": "coll",
9824                            "durability": "transient",
9825                        },
9826                    ],
9827                }
9828            ),
9829            Ok(())
9830        ),
9831        test_cml_service(
9832            json!({
9833                "capabilities": [
9834                    {
9835                        "protocol": "a",
9836                        "path": "/minfs",
9837                    },
9838                    {
9839                        "protocol": "b",
9840                        "path": "/data",
9841                    },
9842                    {
9843                        "protocol": "c",
9844                    },
9845                ],
9846            }),
9847            Ok(())
9848        ),
9849        test_cml_service_multi(
9850            json!({
9851                "capabilities": [
9852                    {
9853                        "service": ["a", "b", "c"],
9854                    },
9855                ],
9856            }),
9857            Ok(())
9858        ),
9859        test_cml_service_multi_invalid_path_no_span(
9860            json!({
9861                "capabilities": [
9862                    {
9863                        "service": ["a", "b", "c"],
9864                        "path": "/minfs",
9865                    },
9866                ],
9867            }),
9868            Err(Error::Validate { err, .. }) if &err == "\"path\" can only be specified when one `service` is supplied."
9869        ),
9870        test_cml_service_all_valid_chars(
9871            json!({
9872                "capabilities": [
9873                    {
9874                        "service": "abcdefghijklmnopqrstuvwxyz0123456789_-service",
9875                    },
9876                ],
9877            }),
9878            Ok(())
9879        ),
9880    }
9881
9882    // Tests structured config
9883    test_validate_cml_with_feature! { FeatureSet::from(vec![]), {
9884        test_cml_configs(
9885            json!({
9886                "config": {
9887                    "verbosity": {
9888                        "type": "string",
9889                        "max_size": 20,
9890                    },
9891                    "timeout": { "type": "uint64" },
9892                    "tags": {
9893                        "type": "vector",
9894                        "max_count": 10,
9895                        "element": {
9896                            "type": "string",
9897                            "max_size": 50
9898                        }
9899                    }
9900                }
9901            }),
9902            Ok(())
9903        ),
9904
9905        test_cml_configs_not_object(
9906            json!({
9907                "config": "abcd"
9908            }),
9909            Err(Error::Parse { err, .. }) if &err == "invalid type: string \"abcd\", expected a map"
9910        ),
9911
9912        test_cml_configs_empty(
9913            json!({
9914                "config": {
9915                }
9916            }),
9917            Err(Error::Validate { err, .. }) if &err == "'config' section is empty"
9918        ),
9919
9920        test_cml_configs_bad_type(
9921            json!({
9922                "config": {
9923                    "verbosity": 123456
9924                }
9925            }),
9926            Err(Error::Parse { err, .. }) if &err == "invalid type: integer `123456`, expected internally tagged enum ConfigValueType"
9927        ),
9928
9929        test_cml_configs_unknown_type(
9930            json!({
9931                "config": {
9932                    "verbosity": {
9933                        "type": "foo"
9934                    }
9935                }
9936            }),
9937            Err(Error::Parse { err, .. }) if &err == "unknown variant `foo`, expected one of `bool`, `uint8`, `uint16`, `uint32`, `uint64`, `int8`, `int16`, `int32`, `int64`, `string`, `vector`"
9938        ),
9939
9940        test_cml_configs_no_max_count_vector(
9941            json!({
9942                "config": {
9943                    "tags": {
9944                        "type": "vector",
9945                        "element": {
9946                            "type": "string",
9947                            "max_size": 50,
9948                        }
9949                    }
9950                }
9951            }),
9952            Err(Error::Parse { err, .. }) if &err == "missing field `max_count`"
9953        ),
9954
9955        test_cml_configs_no_max_size_string(
9956            json!({
9957                "config": {
9958                    "verbosity": {
9959                        "type": "string",
9960                    }
9961                }
9962            }),
9963            Err(Error::Parse { err, .. }) if &err == "missing field `max_size`"
9964        ),
9965
9966        test_cml_configs_no_max_size_string_vector(
9967            json!({
9968                "config": {
9969                    "tags": {
9970                        "type": "vector",
9971                        "max_count": 10,
9972                        "element": {
9973                            "type": "string",
9974                        }
9975                    }
9976                }
9977            }),
9978            Err(Error::Parse { err, .. }) if &err == "missing field `max_size`"
9979        ),
9980
9981        test_cml_configs_empty_key(
9982            json!({
9983                "config": {
9984                    "": {
9985                        "type": "bool"
9986                    }
9987                }
9988            }),
9989            Err(Error::Parse { err, .. }) if &err == "invalid length 0, expected a non-empty name no more than 64 characters in length"
9990        ),
9991
9992        test_cml_configs_too_long_key(
9993            json!({
9994                "config": {
9995                    "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": {
9996                        "type": "bool"
9997                    }
9998                }
9999            }),
10000            Err(Error::Parse { err, .. }) if &err == "invalid length 74, expected a non-empty name no more than 64 characters in length"
10001        ),
10002        test_cml_configs_key_starts_with_number(
10003            json!({
10004                "config": {
10005                    "8abcd": { "type": "uint8" }
10006                }
10007            }),
10008            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"
10009        ),
10010
10011        test_cml_configs_key_ends_with_underscore(
10012            json!({
10013                "config": {
10014                    "abcd_": { "type": "uint8" }
10015                }
10016            }),
10017            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"
10018        ),
10019
10020        test_cml_configs_capitals_in_key(
10021            json!({
10022                "config": {
10023                    "ABCD": { "type": "uint8" }
10024                }
10025            }),
10026            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"
10027        ),
10028
10029        test_cml_configs_special_chars_in_key(
10030            json!({
10031                "config": {
10032                    "!@#$": { "type": "uint8" }
10033                }
10034            }),
10035            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"
10036        ),
10037
10038        test_cml_configs_dashes_in_key(
10039            json!({
10040                "config": {
10041                    "abcd-efgh": { "type": "uint8" }
10042                }
10043            }),
10044            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"
10045        ),
10046
10047        test_cml_configs_bad_max_size_string(
10048            json!({
10049                "config": {
10050                    "verbosity": {
10051                        "type": "string",
10052                        "max_size": "abcd"
10053                    }
10054                }
10055            }),
10056            Err(Error::Parse { err, .. }) if &err == "invalid type: string \"abcd\", expected a nonzero u32"
10057        ),
10058
10059        test_cml_configs_zero_max_size_string(
10060            json!({
10061                "config": {
10062                    "verbosity": {
10063                        "type": "string",
10064                        "max_size": 0
10065                    }
10066                }
10067            }),
10068            Err(Error::Parse { err, .. }) if &err == "invalid value: integer `0`, expected a nonzero u32"
10069        ),
10070
10071        test_cml_configs_bad_max_count_on_vector(
10072            json!({
10073                "config": {
10074                    "toggles": {
10075                        "type": "vector",
10076                        "max_count": "abcd",
10077                        "element": {
10078                            "type": "bool"
10079                        }
10080                    }
10081                }
10082            }),
10083            Err(Error::Parse { err, .. }) if &err == "invalid type: string \"abcd\", expected a nonzero u32"
10084        ),
10085
10086        test_cml_configs_zero_max_count_on_vector(
10087            json!({
10088                "config": {
10089                    "toggles": {
10090                        "type": "vector",
10091                        "max_count": 0,
10092                        "element": {
10093                            "type": "bool"
10094                        }
10095                    }
10096                }
10097            }),
10098            Err(Error::Parse { err, .. }) if &err == "invalid value: integer `0`, expected a nonzero u32"
10099        ),
10100
10101        test_cml_configs_bad_max_size_string_vector(
10102            json!({
10103                "config": {
10104                    "toggles": {
10105                        "type": "vector",
10106                        "max_count": 100,
10107                        "element": {
10108                            "type": "string",
10109                            "max_size": "abcd"
10110                        }
10111                    }
10112                }
10113            }),
10114            Err(Error::Parse { err, .. }) if &err == "invalid type: string \"abcd\", expected a nonzero u32"
10115        ),
10116
10117        test_cml_configs_zero_max_size_string_vector(
10118            json!({
10119                "config": {
10120                    "toggles": {
10121                        "type": "vector",
10122                        "max_count": 100,
10123                        "element": {
10124                            "type": "string",
10125                            "max_size": 0
10126                        }
10127                    }
10128                }
10129            }),
10130            Err(Error::Parse { err, .. }) if &err == "invalid value: integer `0`, expected a nonzero u32"
10131        ),
10132    }}
10133
10134    // Tests the use of `allow_long_names` when the "AllowLongNames" feature is set.
10135    test_validate_cml_with_feature! { FeatureSet::from(vec![Feature::AllowLongNames]), {
10136        test_cml_validate_set_allow_long_names_true(
10137            json!({
10138                "collections": [
10139                    {
10140                        "name": "foo",
10141                        "durability": "transient",
10142                        "allow_long_names": true
10143                    },
10144                ],
10145            }),
10146            Ok(())
10147        ),
10148        test_cml_validate_set_allow_long_names_false(
10149            json!({
10150                "collections": [
10151                    {
10152                        "name": "foo",
10153                        "durability": "transient",
10154                        "allow_long_names": false
10155                    },
10156                ],
10157            }),
10158            Ok(())
10159        ),
10160    }}
10161
10162    // Tests using a dictionary when the UseDictionaries feature is set
10163    test_validate_cml_with_feature! { FeatureSet::from(vec![Feature::UseDictionaries]), {
10164        test_cml_validate_set_allow_use_dictionaries(
10165            json!({
10166                "use": [
10167                    {
10168                        "protocol": "fuchsia.examples.Echo",
10169                        "path": "/svc/fuchsia.examples.Echo",
10170                    },
10171                    {
10172                        "dictionary": "toolbox",
10173                        "path": "/svc",
10174                    },
10175                ],
10176            }),
10177            Ok(())
10178        ),
10179    }}
10180
10181    // Tests that two dictionaries can be used at the same path
10182    test_validate_cml_with_feature! { FeatureSet::from(vec![Feature::UseDictionaries]), {
10183        test_cml_validate_set_allow_use_2_dictionaries_at_same_path(
10184            json!({
10185                "use": [
10186                    {
10187                        "dictionary": "toolbox-1",
10188                        "path": "/svc",
10189                    },
10190                    {
10191                        "dictionary": "toolbox-2",
10192                        "path": "/svc",
10193                    },
10194                ],
10195            }),
10196            Ok(())
10197        ),
10198    }}
10199
10200    // Tests that the use of `allow_long_names` fails when the "AllowLongNames"
10201    // feature is not set.
10202    test_validate_cml! {
10203        test_cml_allow_long_names_without_feature_no_span(
10204            json!({
10205                "collections": [
10206                    {
10207                        "name": "foo",
10208                        "durability": "transient",
10209                        "allow_long_names": true
10210                    },
10211                ],
10212            }),
10213            Err(Error::RestrictedFeature(s)) if s == "allow_long_names"
10214        ),
10215    }
10216
10217    // Tests that using a dictionary fails when the "UseDictionaries" feature is not set.
10218    test_validate_cml! {
10219        test_cml_allow_use_dictionary_without_feature(
10220            json!({
10221                "use": [
10222                    {
10223                        "dictionary": "foo",
10224                        "path": "/foo",
10225                    },
10226                ],
10227            }),
10228            Err(Error::RestrictedFeature(s)) if s == "use_dictionaries"
10229        ),
10230    }
10231
10232    // Tests validate_facets function without the feature set
10233    test_validate_cml! {
10234        test_valid_empty_facets_no_span(
10235            json!({
10236                "facets": {}
10237            }),
10238            Ok(())
10239        ),
10240
10241        test_invalid_empty_facets_no_span(
10242            json!({
10243                "facets": ""
10244            }),
10245            Err(err) if err.to_string().contains("invalid type: string")
10246        ),
10247        test_valid_empty_fuchsia_test_facet_no_span(
10248            json!({
10249                "facets": {TEST_FACET_KEY: {}}
10250            }),
10251            Ok(())
10252        ),
10253
10254        test_valid_allowed_pkg_without_feature_no_span(
10255            json!({
10256                "facets": {
10257                    TEST_TYPE_FACET_KEY: "some_realm",
10258                    TEST_FACET_KEY: {
10259                        TEST_DEPRECATED_ALLOWED_PACKAGES_FACET_KEY: [ "some_pkg" ]
10260                    }
10261                }
10262            }),
10263            Ok(())
10264        ),
10265    }
10266
10267    // Tests validate_facets function without the feature set
10268    test_validate_cml_with_context! {
10269        test_valid_empty_facets(
10270            json!({
10271                "facets": {}
10272            }),
10273            Ok(())
10274        ),
10275
10276        test_invalid_empty_facets(
10277            json!({
10278                "facets": ""
10279            }),
10280            Err(err) if err.to_string().contains("invalid type: string")
10281        ),
10282        test_valid_empty_fuchsia_test_facet(
10283            json!({
10284                "facets": {TEST_FACET_KEY: {}}
10285            }),
10286            Ok(())
10287        ),
10288
10289        test_valid_allowed_pkg_without_feature(
10290            json!({
10291                "facets": {
10292                    TEST_TYPE_FACET_KEY: "some_realm",
10293                    TEST_FACET_KEY: {
10294                        TEST_DEPRECATED_ALLOWED_PACKAGES_FACET_KEY: [ "some_pkg" ]
10295                    }
10296                }
10297            }),
10298            Ok(())
10299        ),
10300    }
10301
10302    // Tests validate_facets function with the RestrictTestTypeInFacet enabled.
10303    test_validate_cml_with_feature! { FeatureSet::from(vec![Feature::RestrictTestTypeInFacet]), {
10304        test_valid_empty_facets_with_test_type_feature_enabled_no_span(
10305            json!({
10306                "facets": {}
10307            }),
10308            Ok(())
10309        ),
10310        test_valid_empty_fuchsia_test_facet_with_test_type_feature_enabled_no_span(
10311            json!({
10312                "facets": {TEST_FACET_KEY: {}}
10313            }),
10314            Ok(())
10315        ),
10316
10317        test_invalid_test_type_with_feature_enabled_no_span(
10318            json!({
10319                "facets": {
10320                    TEST_FACET_KEY: {
10321                        TEST_TYPE_FACET_KEY: "some_realm",
10322                    }
10323                }
10324            }),
10325            Err(err) if err.to_string().contains(TEST_TYPE_FACET_KEY)
10326        ),
10327    }}
10328
10329    // Tests validate_facets function with the RestrictTestTypeInFacet enabled.
10330    test_validate_cml_with_feature_context! { FeatureSet::from(vec![Feature::RestrictTestTypeInFacet]), {
10331        test_valid_empty_facets_with_test_type_feature_enabled(
10332            json!({
10333                "facets": {}
10334            }),
10335            Ok(())
10336        ),
10337        test_valid_empty_fuchsia_test_facet_with_test_type_feature_enabled(
10338            json!({
10339                "facets": {TEST_FACET_KEY: {}}
10340            }),
10341            Ok(())
10342        ),
10343
10344        test_invalid_test_type_with_feature_enabled(
10345            json!({
10346                "facets": {
10347                    TEST_FACET_KEY: {
10348                        TEST_TYPE_FACET_KEY: "some_realm",
10349                    }
10350                }
10351            }),
10352            Err(err) if err.to_string().contains(TEST_TYPE_FACET_KEY)
10353        ),
10354    }}
10355
10356    // Tests validate_facets function with the EnableAllowNonHermeticPackagesFeature disabled.
10357    test_validate_cml_with_feature! { FeatureSet::from(vec![Feature::AllowNonHermeticPackages]), {
10358        test_valid_empty_facets_with_feature_disabled_no_span(
10359            json!({
10360                "facets": {}
10361            }),
10362            Ok(())
10363        ),
10364        test_valid_empty_fuchsia_test_facet_with_feature_disabled_no_span(
10365            json!({
10366                "facets": {TEST_FACET_KEY: {}}
10367            }),
10368            Ok(())
10369        ),
10370
10371        test_valid_allowed_pkg_with_feature_disabled_no_span(
10372            json!({
10373                "facets": {
10374                    TEST_FACET_KEY: {
10375                        TEST_DEPRECATED_ALLOWED_PACKAGES_FACET_KEY: [ "some_pkg" ]
10376                    }
10377                }
10378            }),
10379            Ok(())
10380        ),
10381    }}
10382
10383    // Tests validate_facets function with the EnableAllowNonHermeticPackagesFeature disabled.
10384    test_validate_cml_with_feature_context! { FeatureSet::from(vec![Feature::AllowNonHermeticPackages]), {
10385        test_valid_empty_facets_with_feature_disabled(
10386            json!({
10387                "facets": {}
10388            }),
10389            Ok(())
10390        ),
10391        test_valid_empty_fuchsia_test_facet_with_feature_disabled(
10392            json!({
10393                "facets": {TEST_FACET_KEY: {}}
10394            }),
10395            Ok(())
10396        ),
10397
10398        test_valid_allowed_pkg_with_feature_disabled(
10399            json!({
10400                "facets": {
10401                    TEST_FACET_KEY: {
10402                        TEST_DEPRECATED_ALLOWED_PACKAGES_FACET_KEY: [ "some_pkg" ]
10403                    }
10404                }
10405            }),
10406            Ok(())
10407        ),
10408    }}
10409
10410    // Tests validate_facets function with the EnableAllowNonHermeticPackagesFeature enabled.
10411    test_validate_cml_with_feature! { FeatureSet::from(vec![Feature::EnableAllowNonHermeticPackagesFeature]), {
10412        test_valid_empty_facets_with_feature_enabled_no_span(
10413            json!({
10414                "facets": {}
10415            }),
10416            Ok(())
10417        ),
10418        test_valid_empty_fuchsia_test_facet_with_feature_enabled_no_span(
10419            json!({
10420                "facets": {TEST_FACET_KEY: {}}
10421            }),
10422            Ok(())
10423        ),
10424
10425        test_invalid_allowed_pkg_with_feature_enabled_no_span(
10426            json!({
10427                "facets": {
10428                    TEST_FACET_KEY: {
10429                        TEST_DEPRECATED_ALLOWED_PACKAGES_FACET_KEY: [ "some_pkg" ]
10430                    }
10431                }
10432            }),
10433            Err(err) if err.to_string().contains(&Feature::AllowNonHermeticPackages.to_string())
10434        ),
10435    }}
10436
10437    // Tests validate_facets function with the EnableAllowNonHermeticPackagesFeature enabled.
10438    test_validate_cml_with_feature_context! { FeatureSet::from(vec![Feature::EnableAllowNonHermeticPackagesFeature]), {
10439        test_valid_empty_facets_with_feature_enabled(
10440            json!({
10441                "facets": {}
10442            }),
10443            Ok(())
10444        ),
10445        test_valid_empty_fuchsia_test_facet_with_feature_enabled(
10446            json!({
10447                "facets": {TEST_FACET_KEY: {}}
10448            }),
10449            Ok(())
10450        ),
10451
10452        test_invalid_allowed_pkg_with_feature_enabled(
10453            json!({
10454                "facets": {
10455                    TEST_FACET_KEY: {
10456                        TEST_DEPRECATED_ALLOWED_PACKAGES_FACET_KEY: [ "some_pkg" ]
10457                    }
10458                }
10459            }),
10460            Err(err) if err.to_string().contains(&Feature::AllowNonHermeticPackages.to_string())
10461        ),
10462    }}
10463
10464    // Tests validate_facets function with the feature enabled and allowed pkg feature set.
10465    test_validate_cml_with_feature! { FeatureSet::from(vec![Feature::EnableAllowNonHermeticPackagesFeature, Feature::AllowNonHermeticPackages]), {
10466        test_invalid_empty_facets_with_feature_enabled_no_span(
10467            json!({
10468                "facets": {}
10469            }),
10470            Err(err) if err.to_string().contains(&Feature::AllowNonHermeticPackages.to_string())
10471        ),
10472        test_invalid_empty_fuchsia_test_facet_with_feature_enabled_no_span(
10473            json!({
10474                "facets": {TEST_FACET_KEY: {}}
10475            }),
10476            Err(err) if err.to_string().contains(&Feature::AllowNonHermeticPackages.to_string())
10477        ),
10478
10479        test_valid_allowed_pkg_with_feature_enabled_no_span(
10480            json!({
10481                "facets": {
10482                    TEST_FACET_KEY: {
10483                        TEST_DEPRECATED_ALLOWED_PACKAGES_FACET_KEY: [ "some_pkg" ]
10484                    }
10485                }
10486            }),
10487            Ok(())
10488        ),
10489    }}
10490
10491    // Tests validate_facets function with the feature enabled and allowed pkg feature set.
10492    test_validate_cml_with_feature_context! { FeatureSet::from(vec![Feature::EnableAllowNonHermeticPackagesFeature, Feature::AllowNonHermeticPackages]), {
10493        test_invalid_empty_facets_with_feature_enabled(
10494            json!({
10495                "facets": {}
10496            }),
10497            Err(err) if err.to_string().contains(&Feature::AllowNonHermeticPackages.to_string())
10498        ),
10499
10500        test_invalid_empty_fuchsia_test_facet_with_feature_enabled(
10501            json!({
10502                "facets": {TEST_FACET_KEY: {}}
10503            }),
10504            Err(err) if err.to_string().contains(&Feature::AllowNonHermeticPackages.to_string())
10505        ),
10506
10507        test_valid_allowed_pkg_with_feature_enabled(
10508            json!({
10509                "facets": {
10510                    TEST_FACET_KEY: {
10511                        TEST_DEPRECATED_ALLOWED_PACKAGES_FACET_KEY: [ "some_pkg" ]
10512                    }
10513                }
10514            }),
10515            Ok(())
10516        ),
10517    }}
10518
10519    test_validate_cml_with_feature! { FeatureSet::from(vec![Feature::DynamicDictionaries]), {
10520        test_cml_offer_to_dictionary_unsupported(
10521            json!({
10522                "offer": [
10523                    {
10524                        "event_stream": "p",
10525                        "from": "parent",
10526                        "to": "self/dict",
10527                    },
10528                ],
10529                "capabilities": [
10530                    {
10531                        "dictionary": "dict",
10532                    },
10533                ],
10534            }),
10535            Err(Error::Validate { err, .. }) if &err == "\"offer\" to dictionary \
10536            \"self/dict\" for \"event_stream\" but dictionaries do not support this type yet."
10537        ),
10538        test_cml_dictionary_ref(
10539            json!({
10540                "use": [
10541                    {
10542                        "protocol": "a",
10543                        "from": "parent/a",
10544                    },
10545                    {
10546                        "protocol": "b",
10547                        "from": "#child/a/b",
10548                    },
10549                    {
10550                        "protocol": "c",
10551                        "from": "self/a/b/c",
10552                    },
10553                ],
10554                "capabilities": [
10555                    {
10556                        "dictionary": "a",
10557                    },
10558                ],
10559                "children": [
10560                    {
10561                        "name": "child",
10562                        "url": "fuchsia-pkg://child",
10563                    },
10564                ],
10565            }),
10566            Ok(())
10567        ),
10568        test_cml_expose_dictionary_from_self(
10569            json!({
10570                "expose": [
10571                    {
10572                        "dictionary": "foo_dictionary",
10573                        "from": "self",
10574                    },
10575                ],
10576                "capabilities": [
10577                    {
10578                        "dictionary": "foo_dictionary",
10579                    },
10580                ]
10581            }),
10582            Ok(())
10583        ),
10584        test_cml_offer_to_dictionary_duplicate(
10585            json!({
10586                "offer": [
10587                    {
10588                        "protocol": "p",
10589                        "from": "parent",
10590                        "to": "self/dict",
10591                    },
10592                    {
10593                        "protocol": "p",
10594                        "from": "#child",
10595                        "to": "self/dict",
10596                    },
10597                ],
10598                "capabilities": [
10599                    {
10600                        "dictionary": "dict",
10601                    },
10602                ],
10603                "children": [
10604                    {
10605                        "name": "child",
10606                        "url": "fuchsia-pkg://child",
10607                    },
10608                ],
10609            }),
10610            Err(Error::Validate { err, .. }) if &err == "\"p\" is a duplicate \"offer\" target capability for \"self/dict\""
10611        ),
10612        test_cml_offer_to_dictionary_dynamic(
10613            json!({
10614                "offer": [
10615                    {
10616                        "protocol": "p",
10617                        "from": "parent",
10618                        "to": "self/dict",
10619                    },
10620                ],
10621                "capabilities": [
10622                    {
10623                        "dictionary": "dict",
10624                        "path": "/out/dir",
10625                    },
10626                ],
10627            }),
10628            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."
10629        ),
10630    }}
10631
10632    // Tests that offering and exposing service capabilities to the same target and target name is
10633    // allowed.
10634    test_validate_cml! {
10635        test_cml_aggregate_expose(
10636            json!({
10637                "expose": [
10638                    {
10639                        "service": "fuchsia.foo.Bar",
10640                        "from": ["#a", "#b"],
10641                    },
10642                ],
10643                "children": [
10644                    {
10645                        "name": "a",
10646                        "url": "fuchsia-pkg://fuchsia.com/a#meta/a.cm",
10647                    },
10648                    {
10649                        "name": "b",
10650                        "url": "fuchsia-pkg://fuchsia.com/b#meta/b.cm",
10651                    },
10652                ],
10653            }),
10654            Ok(())
10655        ),
10656        test_cml_aggregate_offer(
10657            json!({
10658                "offer": [
10659                    {
10660                        "service": "fuchsia.foo.Bar",
10661                        "from": ["#a", "#b"],
10662                        "to": "#target",
10663                    },
10664                ],
10665                "children": [
10666                    {
10667                        "name": "a",
10668                        "url": "fuchsia-pkg://fuchsia.com/a#meta/a.cm",
10669                    },
10670                    {
10671                        "name": "b",
10672                        "url": "fuchsia-pkg://fuchsia.com/b#meta/b.cm",
10673                    },
10674                    {
10675                        "name": "target",
10676                        "url": "fuchsia-pkg://fuchsia.com/target#meta/target.cm",
10677                    },
10678                ],
10679            }),
10680            Ok(())
10681        ),
10682    }
10683
10684    use crate::translate::test_util::must_parse_cml;
10685    use crate::translate::{CompileOptions, compile};
10686
10687    #[test]
10688    fn test_cml_use_bad_config_from_self() {
10689        let input = must_parse_cml!({
10690        "use": [
10691            {
10692                "config": "fuchsia.config.MyConfig",
10693                "key": "my_config",
10694                "type": "bool",
10695                "from": "self",
10696            },
10697        ],
10698        });
10699
10700        let options = CompileOptions::new();
10701        assert_matches!(compile(&input, options), Err(Error::Validate { .. }));
10702    }
10703
10704    // Tests for config capabilities
10705    test_validate_cml! {
10706        a_test_cml_use_config(
10707        json!({"use": [
10708            {
10709                "config": "fuchsia.config.MyConfig",
10710                "key": "my_config",
10711                "type": "bool",
10712            },
10713        ],}),
10714        Ok(())
10715        ),
10716        test_cml_use_config_good_vector(
10717        json!({"use": [
10718            {
10719                "config": "fuchsia.config.MyConfig",
10720                "key": "my_config",
10721                "type": "vector",
10722                "element": { "type": "bool"},
10723                "max_count": 1,
10724            },
10725        ],}),
10726        Ok(())
10727        ),
10728        test_cml_use_config_bad_vector_no_span(
10729        json!({"use": [
10730            {
10731                "config": "fuchsia.config.MyConfig",
10732                "key": "my_config",
10733                "type": "vector",
10734                "element": { "type": "bool"},
10735                // Missing max count.
10736            },
10737        ],}),
10738        Err(Error::Validate {err,  .. })
10739        if &err == "Config 'fuchsia.config.MyConfig' is type Vector but is missing field 'max_count'"
10740        ),
10741        test_cml_use_config_bad_string_no_span(
10742        json!({"use": [
10743            {
10744                "config": "fuchsia.config.MyConfig",
10745                "key": "my_config",
10746                "type": "string",
10747                // Missing max size.
10748            },
10749        ],}),
10750        Err(Error::Validate { err, .. })
10751        if &err == "Config 'fuchsia.config.MyConfig' is type String but is missing field 'max_size'"
10752        ),
10753
10754        test_cml_optional_use_no_config(
10755        json!({"use": [
10756            {
10757                "config": "fuchsia.config.MyConfig",
10758                "key": "my_config",
10759                "type": "bool",
10760                "availability": "optional",
10761            },
10762        ],}),
10763        Err(Error::Validate {err, ..})
10764        if &err == "Optionally using a config capability without a default requires a matching 'config' section."
10765        ),
10766        test_cml_transitional_use_no_config(
10767        json!({"use": [
10768            {
10769                "config": "fuchsia.config.MyConfig",
10770                "key": "my_config",
10771                "type": "bool",
10772                "availability": "transitional",
10773            },
10774        ],}),
10775        Err(Error::Validate {err, ..})
10776        if &err == "Optionally using a config capability without a default requires a matching 'config' section."
10777        ),
10778        test_cml_optional_use_bad_type(
10779        json!({"use": [
10780            {
10781                "config": "fuchsia.config.MyConfig",
10782                "key": "my_config",
10783                "type": "bool",
10784                "availability": "optional",
10785            },
10786        ],
10787        "config": {
10788            "my_config": { "type": "uint8"}
10789        }}),
10790        Err(Error::Validate {err, ..})
10791        if &err == "Use and config block differ on type for key 'my_config'"
10792        ),
10793
10794        test_config_required_with_default_no_span(
10795        json!({"use": [
10796            {
10797                "config": "fuchsia.config.MyConfig",
10798                "key": "my_config",
10799                "type": "bool",
10800                "default": "true",
10801            },
10802        ]}),
10803        Err(Error::Validate {err, ..})
10804        if &err == "Config 'fuchsia.config.MyConfig' is required and has a default value"
10805        ),
10806
10807        test_cml_optional_use_good(
10808        json!({"use": [
10809            {
10810                "config": "fuchsia.config.MyConfig",
10811                "key": "my_config",
10812                "type": "bool",
10813                "availability": "optional",
10814            },
10815        ],
10816        "config": {
10817            "my_config": { "type": "bool"},
10818        }
10819    }),
10820    Ok(())
10821        ),
10822    test_cml_use_two_types_bad_no_span(
10823        json!({"use": [
10824            {
10825                "protocol": "fuchsia.protocol.MyProtocol",
10826                "service": "fuchsia.service.MyService",
10827            },
10828        ],
10829    }),
10830        Err(Error::Validate {err, ..})
10831        if &err == "use declaration has multiple capability types defined: [\"service\", \"protocol\"]"
10832        ),
10833    test_cml_offer_two_types_bad(
10834        json!({"offer": [
10835            {
10836                "protocol": "fuchsia.protocol.MyProtocol",
10837                "service": "fuchsia.service.MyService",
10838                "from": "self",
10839                "to" : "#child",
10840            },
10841        ],
10842    }),
10843        Err(Error::Validate {err, ..})
10844        if &err == "offer declaration has multiple capability types defined: [\"service\", \"protocol\"]"
10845        ),
10846    test_cml_expose_two_types_bad_no_span(
10847        json!({"expose": [
10848            {
10849                "protocol": "fuchsia.protocol.MyProtocol",
10850                "service": "fuchsia.service.MyService",
10851                "from" : "self",
10852            },
10853        ],
10854    }),
10855        Err(Error::Validate {err, ..})
10856        if &err == "expose declaration has multiple capability types defined: [\"service\", \"protocol\"]"
10857        ),
10858    }
10859}