cm_fidl_validator/
lib.rs

1// Copyright 2019 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
5pub(crate) mod util;
6
7pub mod error;
8
9pub use crate::util::check_url;
10
11use crate::error::*;
12use crate::util::*;
13use cm_graph::DependencyNode;
14use cm_types::IterablePath;
15use directed_graph::DirectedGraph;
16use fidl_fuchsia_component_decl as fdecl;
17use itertools::Itertools;
18use std::collections::{BTreeSet, HashMap, HashSet};
19use std::path::Path;
20
21trait HasAvailability {
22    fn availability(&self) -> fdecl::Availability;
23}
24
25impl HasAvailability for fdecl::ExposeService {
26    fn availability(&self) -> fdecl::Availability {
27        return self.availability.unwrap_or(fdecl::Availability::Required);
28    }
29}
30
31impl HasAvailability for fdecl::OfferService {
32    fn availability(&self) -> fdecl::Availability {
33        return self.availability.unwrap_or(fdecl::Availability::Required);
34    }
35}
36
37/// Validates Configuration Value Spec.
38///
39/// For now, this simply verifies that all semantically required fields are present.
40pub fn validate_value_spec(spec: &fdecl::ConfigValueSpec) -> Result<(), ErrorList> {
41    let mut errors = vec![];
42    if let Some(value) = &spec.value {
43        match value {
44            fdecl::ConfigValue::Single(s) => match s {
45                fdecl::ConfigSingleValue::Bool(_)
46                | fdecl::ConfigSingleValue::Uint8(_)
47                | fdecl::ConfigSingleValue::Uint16(_)
48                | fdecl::ConfigSingleValue::Uint32(_)
49                | fdecl::ConfigSingleValue::Uint64(_)
50                | fdecl::ConfigSingleValue::Int8(_)
51                | fdecl::ConfigSingleValue::Int16(_)
52                | fdecl::ConfigSingleValue::Int32(_)
53                | fdecl::ConfigSingleValue::Int64(_)
54                | fdecl::ConfigSingleValue::String(_) => {}
55                fdecl::ConfigSingleValueUnknown!() => {
56                    errors.push(Error::invalid_field(DeclType::ConfigValueSpec, "value"));
57                }
58            },
59            fdecl::ConfigValue::Vector(l) => match l {
60                fdecl::ConfigVectorValue::BoolVector(_)
61                | fdecl::ConfigVectorValue::Uint8Vector(_)
62                | fdecl::ConfigVectorValue::Uint16Vector(_)
63                | fdecl::ConfigVectorValue::Uint32Vector(_)
64                | fdecl::ConfigVectorValue::Uint64Vector(_)
65                | fdecl::ConfigVectorValue::Int8Vector(_)
66                | fdecl::ConfigVectorValue::Int16Vector(_)
67                | fdecl::ConfigVectorValue::Int32Vector(_)
68                | fdecl::ConfigVectorValue::Int64Vector(_)
69                | fdecl::ConfigVectorValue::StringVector(_) => {}
70                fdecl::ConfigVectorValueUnknown!() => {
71                    errors.push(Error::invalid_field(DeclType::ConfigValueSpec, "value"));
72                }
73            },
74            fdecl::ConfigValueUnknown!() => {
75                errors.push(Error::invalid_field(DeclType::ConfigValueSpec, "value"));
76            }
77        }
78    } else {
79        errors.push(Error::missing_field(DeclType::ConfigValueSpec, "value"));
80    }
81
82    if errors.is_empty() { Ok(()) } else { Err(ErrorList::new(errors)) }
83}
84
85/// Validates Configuration Values Data.
86///
87/// The Value Data may ultimately originate from a CVF file, or be directly constructed by the
88/// caller. Either way, Value Data should always be validated before it's used. For now, this
89/// simply verifies that all semantically required fields are present.
90///
91/// This method does not validate value data against a configuration schema.
92pub fn validate_values_data(data: &fdecl::ConfigValuesData) -> Result<(), ErrorList> {
93    let mut errors = vec![];
94    if let Some(values) = &data.values {
95        for spec in values {
96            if let Err(mut e) = validate_value_spec(spec) {
97                errors.append(&mut e.errs);
98            }
99        }
100    } else {
101        errors.push(Error::missing_field(DeclType::ConfigValuesData, "values"));
102    }
103
104    if let Some(checksum) = &data.checksum {
105        match checksum {
106            fdecl::ConfigChecksum::Sha256(_) => {}
107            fdecl::ConfigChecksumUnknown!() => {
108                errors.push(Error::invalid_field(DeclType::ConfigValuesData, "checksum"));
109            }
110        }
111    } else {
112        errors.push(Error::missing_field(DeclType::ConfigValuesData, "checksum"));
113    }
114
115    if errors.is_empty() { Ok(()) } else { Err(ErrorList::new(errors)) }
116}
117
118// `fdecl::Ref` is not hashable, so define this equivalent type for use in maps
119#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
120enum RefKey<'a> {
121    Parent,
122    Self_,
123    Child(&'a str),
124    Collection(&'a str),
125    Framework,
126    Capability,
127    Debug,
128}
129
130/// Validates a Component.
131///
132/// The Component may ultimately originate from a CM file, or be directly constructed by the
133/// caller. Either way, a Component should always be validated before it's used. Examples
134/// of what is validated (which may evolve in the future):
135///
136/// - That all semantically required fields are present
137/// - That a child_name referenced in a source actually exists in the list of children
138/// - That there are no duplicate target paths.
139/// - That only weak-dependency capabilities may be offered back to the
140///   component that exposed them.
141///
142/// All checks are local to this Component.
143pub fn validate(decl: &fdecl::Component) -> Result<(), ErrorList> {
144    let ctx = ValidationContext::default();
145    ctx.validate(decl, &HashSet::new(), &vec![]).map_err(|errs| ErrorList::new(errs))
146}
147
148/// Validates a list of namespace or builtin Capabilities.
149fn validate_capabilities(
150    capabilities: &[fdecl::Capability],
151    as_builtin: bool,
152) -> Result<(), ErrorList> {
153    let mut ctx = ValidationContext::default();
154
155    ctx.load_dictionary_names(capabilities.iter().filter_map(|capability| match capability {
156        fdecl::Capability::Dictionary(dictionary_decl) => Some(dictionary_decl),
157        _ => None,
158    }));
159
160    ctx.validate_capability_decls(capabilities, as_builtin);
161    if ctx.errors.is_empty() { Ok(()) } else { Err(ErrorList::new(ctx.errors)) }
162}
163
164// Validate builtin capabilities.
165pub fn validate_builtin_capabilities(
166    capabilities: &Vec<fdecl::Capability>,
167) -> Result<(), ErrorList> {
168    validate_capabilities(capabilities, true)
169}
170
171// Validate namespace capabilities.
172pub fn validate_namespace_capabilities(
173    capabilities: &Vec<fdecl::Capability>,
174) -> Result<(), ErrorList> {
175    validate_capabilities(capabilities, false)
176}
177
178/// An interface to call into either `check_dynamic_name()` or `check_name()`, depending on the context
179/// of the caller.
180type CheckChildNameFn = fn(Option<&String>, DeclType, &str, &mut Vec<Error>) -> bool;
181
182pub fn validate_dynamic_child(child: &fdecl::Child) -> Result<(), ErrorList> {
183    let mut errors = vec![];
184
185    if let Err(mut error_list) = validate_child(child, check_dynamic_name) {
186        errors.append(&mut error_list.errs);
187    }
188
189    if child.environment.is_some() {
190        errors.push(Error::DynamicChildWithEnvironment);
191    }
192
193    if errors.is_empty() { Ok(()) } else { Err(ErrorList { errs: errors }) }
194}
195
196/// Validates an independent Child. Performs the same validation on it as `validate`. A
197/// `check_name_fn` is passed into specify the function used to validate the child name.
198fn validate_child(
199    child: &fdecl::Child,
200    check_child_name: CheckChildNameFn,
201) -> Result<(), ErrorList> {
202    let mut errors = vec![];
203    check_child_name(child.name.as_ref(), DeclType::Child, "name", &mut errors);
204    check_url(child.url.as_ref(), DeclType::Child, "url", &mut errors);
205    if child.startup.is_none() {
206        errors.push(Error::missing_field(DeclType::Child, "startup"));
207    }
208    // Allow `on_terminate` to be unset since the default is almost always desired.
209    if child.environment.is_some() {
210        check_name(child.environment.as_ref(), DeclType::Child, "environment", &mut errors);
211    }
212    if errors.is_empty() { Ok(()) } else { Err(ErrorList { errs: errors }) }
213}
214
215/// Validates a collection of dynamic offers. Dynamic offers differ from static
216/// offers, in that
217///
218/// 1. a dynamic offer's `target` field must be omitted;
219/// 2. a dynamic offer's `source` _may_ be a dynamic child;
220/// 3. since this crate isn't really designed to handle dynamic children, we
221///    disable the checks that ensure that the source/target exist, and that the
222///    offers don't introduce any cycles.
223pub fn validate_dynamic_offers<'a>(
224    dynamic_children: Vec<(&'a str, &'a str)>,
225    existing_dynamic_offers: &HashSet<(DependencyNode, DependencyNode)>,
226    new_dynamic_offers: &'a Vec<fdecl::Offer>,
227    decl: &'a fdecl::Component,
228) -> Result<(), ErrorList> {
229    let mut ctx = ValidationContext::default();
230    ctx.dynamic_children = dynamic_children;
231    ctx.validate(decl, existing_dynamic_offers, new_dynamic_offers)
232        .map_err(|errs| ErrorList::new(errs))
233}
234
235fn check_offer_name(
236    prop: Option<&String>,
237    decl: DeclType,
238    keyword: &str,
239    offer_type: OfferType,
240    errors: &mut Vec<Error>,
241) -> bool {
242    if offer_type == OfferType::Dynamic {
243        check_dynamic_name(prop, decl, keyword, errors)
244    } else {
245        check_name(prop, decl, keyword, errors)
246    }
247}
248
249#[derive(Default)]
250struct ValidationContext<'a> {
251    all_children: HashMap<&'a str, &'a fdecl::Child>,
252    all_collections: HashSet<&'a str>,
253    all_capability_ids: HashSet<&'a str>,
254    all_storages: HashMap<&'a str, Option<&'a fdecl::Ref>>,
255    all_services: HashSet<&'a str>,
256    all_protocols: HashSet<&'a str>,
257    all_directories: HashSet<&'a str>,
258    all_runners: HashSet<&'a str>,
259    all_resolvers: HashSet<&'a str>,
260    all_dictionaries: HashMap<&'a str, &'a fdecl::Dictionary>,
261
262    #[cfg(fuchsia_api_level_at_least = "HEAD")]
263    all_configs: HashSet<&'a str>,
264
265    all_environment_names: HashSet<&'a str>,
266    dynamic_children: Vec<(&'a str, &'a str)>,
267    strong_dependencies: DirectedGraph<DependencyNode>,
268    target_ids: IdMap<'a>,
269    errors: Vec<Error>,
270}
271
272/// [Container] provides a capability type agnostic trait to check for the existence of a
273/// capability definition of a particular type. This is useful for writing common validation
274/// functions.
275trait Container {
276    fn contains(&self, key: &str) -> bool;
277}
278
279impl<'a> Container for HashSet<&'a str> {
280    fn contains(&self, key: &str) -> bool {
281        self.contains(key)
282    }
283}
284
285impl<'a, T> Container for HashMap<&'a str, T> {
286    fn contains(&self, key: &str) -> bool {
287        self.contains_key(key)
288    }
289}
290
291impl<'a> ValidationContext<'a> {
292    fn validate(
293        mut self,
294        decl: &'a fdecl::Component,
295        existing_dynamic_offers: &HashSet<(DependencyNode, DependencyNode)>,
296        new_dynamic_offers: &'a Vec<fdecl::Offer>,
297    ) -> Result<(), Vec<Error>> {
298        // Collect all environment names first, so that references to them can be checked.
299        if let Some(envs) = &decl.environments {
300            self.collect_environment_names(&envs);
301        }
302
303        // Validate "children" and build the set of all children.
304        if let Some(children) = decl.children.as_ref() {
305            for child in children {
306                self.validate_child_decl(&child);
307            }
308        }
309
310        // Validate "collections" and build the set of all collections.
311        if let Some(collections) = decl.collections.as_ref() {
312            for collection in collections {
313                self.validate_collection_decl(&collection);
314            }
315        }
316
317        // Validate "capabilities" and build the set of all capabilities.
318        if let Some(capabilities) = decl.capabilities.as_ref() {
319            self.load_dictionary_names(capabilities.iter().filter_map(
320                |capability| match capability {
321                    fdecl::Capability::Dictionary(dictionary_decl) => Some(dictionary_decl),
322                    _ => None,
323                },
324            ));
325            self.validate_capability_decls(capabilities, false);
326        }
327
328        // Validate "uses".
329        let mut use_runner_name = None;
330        let mut use_runner_source = None;
331        if let Some(uses) = decl.uses.as_ref() {
332            (use_runner_name, use_runner_source) = self.validate_use_decls(uses);
333        }
334
335        // Validate "program".
336        if let Some(program) = decl.program.as_ref() {
337            self.validate_program(program, use_runner_name, use_runner_source);
338        }
339
340        // Validate "exposes".
341        if let Some(exposes) = decl.exposes.as_ref() {
342            let mut expose_to_parent_ids = HashMap::new();
343            let mut expose_to_framework_ids = HashMap::new();
344            for expose in exposes.iter() {
345                self.validate_expose_decl(
346                    &expose,
347                    &mut expose_to_parent_ids,
348                    &mut expose_to_framework_ids,
349                );
350            }
351            self.validate_expose_group(&exposes);
352        }
353
354        // Validate "offers".
355        if let Some(offers) = decl.offers.as_ref() {
356            for offer in offers.iter() {
357                self.validate_offer_decl(&offer, OfferType::Static);
358            }
359            self.validate_offer_group(&offers, OfferType::Static);
360        }
361
362        for dynamic_offer in new_dynamic_offers {
363            self.validate_offer_decl(dynamic_offer, OfferType::Dynamic);
364            cm_graph::add_dependencies_from_offer(
365                &mut self.strong_dependencies,
366                dynamic_offer,
367                &self.dynamic_children,
368            );
369        }
370        self.validate_offer_group(new_dynamic_offers, OfferType::Dynamic);
371
372        // Validate "environments" after all other declarations are processed.
373        if let Some(environment) = decl.environments.as_ref() {
374            for environment in environment {
375                self.validate_environment_decl(&environment);
376            }
377        }
378
379        // Validate "config"
380        self.validate_config(decl.config.as_ref(), decl.uses.as_ref());
381
382        // Check that there are no strong cyclical dependencies
383        cm_graph::generate_dependency_graph(
384            &mut self.strong_dependencies,
385            &decl,
386            &self.dynamic_children,
387            existing_dynamic_offers.clone(),
388        );
389        if let Err(e) = self.strong_dependencies.topological_sort() {
390            self.errors.push(Error::dependency_cycle(e.format_cycle()));
391        }
392
393        if self.errors.is_empty() { Ok(()) } else { Err(self.errors) }
394    }
395
396    /// Collects all the environment names, watching for duplicates.
397    fn collect_environment_names(&mut self, envs: &'a [fdecl::Environment]) {
398        for env in envs {
399            if let Some(name) = env.name.as_ref() {
400                if !self.all_environment_names.insert(name) {
401                    self.errors.push(Error::duplicate_field(DeclType::Environment, "name", name));
402                }
403            }
404        }
405    }
406
407    // Validates a config schema. Checks that each field's layout matches the expected constraints
408    // and properties.
409    fn validate_config(
410        &mut self,
411        config: Option<&fdecl::ConfigSchema>,
412        uses: Option<&Vec<fdecl::Use>>,
413    ) {
414        use std::collections::BTreeMap;
415
416        // Get all of the `use` configs that are optional without a default.
417        let optional_use_keys: BTreeMap<String, fdecl::ConfigType> =
418            uses.map_or(BTreeMap::new(), |u| {
419                u.iter()
420                    .map(|u| {
421                        let fdecl::Use::Config(config) = u else {
422                            return None;
423                        };
424                        if config.availability == Some(fdecl::Availability::Required)
425                            || config.availability == None
426                        {
427                            return None;
428                        }
429                        if let Some(_) = config.default.as_ref() {
430                            return None;
431                        }
432                        let Some(key) = config.target_name.clone() else {
433                            return None;
434                        };
435                        let Some(value) = config.type_.clone() else {
436                            return None;
437                        };
438                        Some((key, value))
439                    })
440                    .flatten()
441                    .collect()
442            });
443
444        // Validate default values in use configs.
445        for u in uses.iter().flat_map(|x| x.iter()) {
446            let fdecl::Use::Config(config) = u else { continue };
447            let Some(default) = config.default.as_ref() else { continue };
448            validate_value_spec(&fdecl::ConfigValueSpec {
449                value: Some(default.clone()),
450                ..Default::default()
451            })
452            .map_err(|mut e| self.errors.append(&mut e.errs))
453            .ok();
454        }
455
456        let Some(config) = config else {
457            if !optional_use_keys.is_empty() {
458                self.errors.push(Error::missing_field(DeclType::ConfigField, "config"))
459            }
460            return;
461        };
462
463        if let Some(fields) = &config.fields {
464            for field in fields {
465                if field.key.is_none() {
466                    self.errors.push(Error::missing_field(DeclType::ConfigField, "key"));
467                }
468                if let Some(type_) = &field.type_ {
469                    self.validate_config_type(type_, true);
470                } else {
471                    self.errors.push(Error::missing_field(DeclType::ConfigField, "value_type"));
472                }
473            }
474        } else {
475            self.errors.push(Error::missing_field(DeclType::ConfigSchema, "fields"));
476        }
477
478        if let Some(checksum) = &config.checksum {
479            match checksum {
480                fdecl::ConfigChecksum::Sha256(_) => {}
481                fdecl::ConfigChecksumUnknown!() => {
482                    self.errors.push(Error::invalid_field(DeclType::ConfigSchema, "checksum"));
483                }
484            }
485        } else {
486            self.errors.push(Error::missing_field(DeclType::ConfigSchema, "checksum"));
487        }
488
489        'outer: for (key, value) in optional_use_keys.iter() {
490            for field in config.fields.iter().flatten() {
491                if field.key.as_ref() == Some(key) {
492                    if field.type_.as_ref() != Some(value) {
493                        self.errors.push(Error::invalid_field(DeclType::ConfigField, key.clone()));
494                    }
495                    continue 'outer;
496                }
497            }
498            self.errors.push(Error::missing_field(DeclType::ConfigField, key.clone()));
499        }
500
501        match config.value_source {
502            None => self.errors.push(Error::missing_field(DeclType::ConfigSchema, "value_source")),
503            #[cfg(fuchsia_api_level_at_least = "HEAD")]
504            Some(fdecl::ConfigValueSource::Capabilities(_)) => {
505                if !optional_use_keys.is_empty() {
506                    self.errors
507                        .push(Error::invalid_field(DeclType::ConfigValueSource, "ValueSource"))
508                }
509            }
510            Some(fdecl::ConfigValueSource::__SourceBreaking { .. }) => {
511                self.errors.push(Error::invalid_field(DeclType::ConfigValueSource, "ValueSource"))
512            }
513            _ => (),
514        };
515    }
516
517    fn validate_config_type(&mut self, type_: &fdecl::ConfigType, accept_vectors: bool) {
518        match &type_.layout {
519            fdecl::ConfigTypeLayout::Bool
520            | fdecl::ConfigTypeLayout::Uint8
521            | fdecl::ConfigTypeLayout::Uint16
522            | fdecl::ConfigTypeLayout::Uint32
523            | fdecl::ConfigTypeLayout::Uint64
524            | fdecl::ConfigTypeLayout::Int8
525            | fdecl::ConfigTypeLayout::Int16
526            | fdecl::ConfigTypeLayout::Int32
527            | fdecl::ConfigTypeLayout::Int64 => {
528                // These layouts have no parameters or constraints
529                if let Some(parameters) = &type_.parameters {
530                    if !parameters.is_empty() {
531                        self.errors
532                            .push(Error::extraneous_field(DeclType::ConfigType, "parameters"));
533                    }
534                } else {
535                    self.errors.push(Error::missing_field(DeclType::ConfigType, "parameters"));
536                }
537
538                if !type_.constraints.is_empty() {
539                    self.errors.push(Error::extraneous_field(DeclType::ConfigType, "constraints"));
540                }
541            }
542            fdecl::ConfigTypeLayout::String => {
543                // String has exactly one constraint and no parameter
544                if let Some(parameters) = &type_.parameters {
545                    if !parameters.is_empty() {
546                        self.errors
547                            .push(Error::extraneous_field(DeclType::ConfigType, "parameters"));
548                    }
549                } else {
550                    self.errors.push(Error::missing_field(DeclType::ConfigType, "parameters"));
551                }
552
553                if type_.constraints.is_empty() {
554                    self.errors.push(Error::missing_field(DeclType::ConfigType, "constraints"));
555                } else if type_.constraints.len() > 1 {
556                    self.errors.push(Error::extraneous_field(DeclType::ConfigType, "constraints"));
557                } else if let fdecl::LayoutConstraint::MaxSize(_) = &type_.constraints[0] {
558                } else {
559                    self.errors.push(Error::invalid_field(DeclType::ConfigType, "constraints"));
560                }
561            }
562            fdecl::ConfigTypeLayout::Vector => {
563                if accept_vectors {
564                    // Vector has exactly one constraint and one parameter
565                    if let Some(parameters) = &type_.parameters {
566                        if parameters.is_empty() {
567                            self.errors
568                                .push(Error::missing_field(DeclType::ConfigType, "parameters"));
569                        } else if parameters.len() > 1 {
570                            self.errors
571                                .push(Error::extraneous_field(DeclType::ConfigType, "parameters"));
572                        } else if let fdecl::LayoutParameter::NestedType(nested_type) =
573                            &parameters[0]
574                        {
575                            self.validate_config_type(nested_type, false);
576                        } else {
577                            self.errors
578                                .push(Error::invalid_field(DeclType::ConfigType, "parameters"));
579                        }
580                    } else {
581                        self.errors.push(Error::missing_field(DeclType::ConfigType, "parameters"))
582                    }
583
584                    if type_.constraints.is_empty() {
585                        self.errors.push(Error::missing_field(DeclType::ConfigType, "constraints"));
586                    } else if type_.constraints.len() > 1 {
587                        self.errors
588                            .push(Error::extraneous_field(DeclType::ConfigType, "constraints"));
589                    } else if let fdecl::LayoutConstraint::MaxSize(_) = &type_.constraints[0] {
590                    } else {
591                        self.errors.push(Error::invalid_field(DeclType::ConfigType, "constraints"));
592                    }
593                } else {
594                    self.errors.push(Error::nested_vector());
595                }
596            }
597            _ => self.errors.push(Error::invalid_field(DeclType::ConfigType, "layout")),
598        }
599    }
600
601    fn validate_capability_decls(
602        &mut self,
603        capabilities: &'a [fdecl::Capability],
604        as_builtin: bool,
605    ) {
606        for capability in capabilities {
607            self.validate_capability_decl(capability, as_builtin);
608        }
609    }
610
611    /// Validates an individual capability declaration as either a built-in capability or (if
612    /// `as_builtin = false`) as a component or namespace capability.
613    // Storage capabilities are not currently allowed as built-ins, but there's no deep reason for this.
614    // Update this method to allow built-in storage capabilities as needed.
615    fn validate_capability_decl(&mut self, capability: &'a fdecl::Capability, as_builtin: bool) {
616        match capability {
617            fdecl::Capability::Service(service) => self.validate_service_decl(&service, as_builtin),
618            fdecl::Capability::Protocol(protocol) => {
619                self.validate_protocol_decl(&protocol, as_builtin)
620            }
621            fdecl::Capability::Directory(directory) => {
622                self.validate_directory_decl(&directory, as_builtin)
623            }
624            fdecl::Capability::Storage(storage) => {
625                if as_builtin {
626                    self.errors.push(Error::CapabilityCannotBeBuiltin(DeclType::Storage))
627                } else {
628                    self.validate_storage_decl(&storage)
629                }
630            }
631            fdecl::Capability::Runner(runner) => self.validate_runner_decl(&runner, as_builtin),
632            fdecl::Capability::Resolver(resolver) => {
633                self.validate_resolver_decl(&resolver, as_builtin)
634            }
635            fdecl::Capability::EventStream(event) => {
636                if as_builtin {
637                    self.validate_event_stream_decl(&event)
638                } else {
639                    self.errors.push(Error::CapabilityMustBeBuiltin(DeclType::EventStream))
640                }
641            }
642            fdecl::Capability::Dictionary(dictionary) => {
643                self.validate_dictionary_decl(&dictionary);
644            }
645            #[cfg(fuchsia_api_level_at_least = "HEAD")]
646            fdecl::Capability::Config(config) => {
647                self.validate_configuration_decl(&config);
648            }
649            fdecl::CapabilityUnknown!() => self.errors.push(Error::UnknownCapability),
650        }
651    }
652
653    /// Returns the `source_name` and `source` of the runner in `uses`, if present.
654    fn validate_use_decls(
655        &mut self,
656        uses: &'a [fdecl::Use],
657    ) -> (Option<&'a String>, Option<&'a fdecl::Ref>) {
658        // Validate individual fields.
659        for use_ in uses.iter() {
660            self.validate_use_decl(&use_);
661        }
662        self.validate_use_paths(&uses);
663
664        #[cfg(fuchsia_api_level_at_least = "HEAD")]
665        {
666            let mut use_runner_name = None;
667            let mut use_runner_source = None;
668            for use_ in uses.iter() {
669                if let fdecl::Use::Runner(use_runner) = use_ {
670                    if use_runner_name.is_some() {
671                        self.errors.push(Error::MultipleRunnersUsed);
672                    }
673
674                    use_runner_name = use_runner.source_name.as_ref();
675                    use_runner_source = use_runner.source.as_ref();
676                }
677            }
678            return (use_runner_name, use_runner_source);
679        }
680        #[cfg(fuchsia_api_level_less_than = "HEAD")]
681        return (None, None);
682    }
683
684    fn validate_use_decl(&mut self, use_: &'a fdecl::Use) {
685        match use_ {
686            fdecl::Use::Service(u) => {
687                let decl = DeclType::UseService;
688                self.validate_use_fields(
689                    decl,
690                    Self::service_checker,
691                    u.source.as_ref(),
692                    u.source_name.as_ref(),
693                    u.source_dictionary.as_ref(),
694                    u.target_path.as_ref(),
695                    u.dependency_type.as_ref(),
696                    u.availability.as_ref(),
697                );
698                if u.dependency_type.is_none() {
699                    self.errors.push(Error::missing_field(decl, "dependency_type"));
700                }
701            }
702            fdecl::Use::Protocol(u) => {
703                let decl = DeclType::UseProtocol;
704                self.validate_use_fields(
705                    decl,
706                    Self::protocol_checker,
707                    u.source.as_ref(),
708                    u.source_name.as_ref(),
709                    u.source_dictionary.as_ref(),
710                    u.target_path.as_ref(),
711                    u.dependency_type.as_ref(),
712                    u.availability.as_ref(),
713                );
714                if u.dependency_type.is_none() {
715                    self.errors.push(Error::missing_field(decl, "dependency_type"));
716                }
717            }
718            fdecl::Use::Directory(u) => {
719                let decl = DeclType::UseDirectory;
720                self.validate_use_fields(
721                    decl,
722                    Self::directory_checker,
723                    u.source.as_ref(),
724                    u.source_name.as_ref(),
725                    u.source_dictionary.as_ref(),
726                    u.target_path.as_ref(),
727                    u.dependency_type.as_ref(),
728                    u.availability.as_ref(),
729                );
730                if u.dependency_type.is_none() {
731                    self.errors.push(Error::missing_field(decl, "dependency_type"));
732                }
733                if u.rights.is_none() {
734                    self.errors.push(Error::missing_field(DeclType::UseDirectory, "rights"));
735                }
736                if let Some(subdir) = u.subdir.as_ref() {
737                    check_relative_path(
738                        Some(subdir),
739                        DeclType::UseDirectory,
740                        "subdir",
741                        &mut self.errors,
742                    );
743                }
744            }
745            fdecl::Use::Storage(u) => {
746                const SOURCE: Option<fdecl::Ref> = Some(fdecl::Ref::Parent(fdecl::ParentRef {}));
747                const DEPENDENCY_TYPE: Option<fdecl::DependencyType> =
748                    Some(fdecl::DependencyType::Strong);
749                self.validate_use_fields(
750                    DeclType::UseStorage,
751                    Self::storage_checker,
752                    SOURCE.as_ref(),
753                    u.source_name.as_ref(),
754                    None,
755                    u.target_path.as_ref(),
756                    DEPENDENCY_TYPE.as_ref(),
757                    u.availability.as_ref(),
758                );
759            }
760            fdecl::Use::EventStream(u) => {
761                const DEPENDENCY_TYPE: Option<fdecl::DependencyType> =
762                    Some(fdecl::DependencyType::Strong);
763                let decl = DeclType::UseEventStream;
764                self.validate_use_fields(
765                    decl,
766                    Self::event_stream_checker,
767                    u.source.as_ref(),
768                    u.source_name.as_ref(),
769                    None,
770                    u.target_path.as_ref(),
771                    DEPENDENCY_TYPE.as_ref(),
772                    u.availability.as_ref(),
773                );
774                // Additional validation.
775                match u.source {
776                    Some(fdecl::Ref::Child(_)) | Some(fdecl::Ref::Parent(_)) => {
777                        // Allowed.
778                    }
779                    Some(fdecl::Ref::Framework(_))
780                    | Some(fdecl::Ref::Self_(_))
781                    | Some(fdecl::Ref::Debug(_)) => {
782                        // Allowed in general but not for event streams, add an error.
783                        self.errors.push(Error::invalid_field(decl, "source"));
784                    }
785                    Some(fdecl::Ref::Collection(_)) | Some(fdecl::RefUnknown!()) | None => {
786                        // Already handled by validate_use_fields.
787                    }
788                }
789                if let Some(scope) = &u.scope {
790                    for reference in scope {
791                        if !matches!(reference, fdecl::Ref::Child(_) | fdecl::Ref::Collection(_)) {
792                            self.errors.push(Error::invalid_field(decl, "scope"));
793                        }
794                    }
795                }
796            }
797            #[cfg(fuchsia_api_level_at_least = "HEAD")]
798            fdecl::Use::Runner(u) => {
799                const DEPENDENCY_TYPE: Option<fdecl::DependencyType> =
800                    Some(fdecl::DependencyType::Strong);
801                const AVAILABILITY: Option<fdecl::Availability> =
802                    Some(fdecl::Availability::Required);
803                let decl = DeclType::UseRunner;
804                self.validate_use_fields(
805                    decl,
806                    Self::runner_checker,
807                    u.source.as_ref(),
808                    u.source_name.as_ref(),
809                    u.source_dictionary.as_ref(),
810                    None,
811                    DEPENDENCY_TYPE.as_ref(),
812                    AVAILABILITY.as_ref(),
813                );
814            }
815            #[cfg(fuchsia_api_level_at_least = "HEAD")]
816            fdecl::Use::Config(u) => {
817                const DEPENDENCY_TYPE: Option<fdecl::DependencyType> =
818                    Some(fdecl::DependencyType::Strong);
819                let decl = DeclType::UseConfiguration;
820                self.validate_use_fields(
821                    decl,
822                    Self::config_checker,
823                    u.source.as_ref(),
824                    u.source_name.as_ref(),
825                    None,
826                    None,
827                    DEPENDENCY_TYPE.as_ref(),
828                    u.availability.as_ref(),
829                );
830            }
831            fdecl::UseUnknown!() => {
832                self.errors.push(Error::invalid_field(DeclType::Component, "use"));
833            }
834        }
835    }
836
837    /// Validates the "program" declaration. This does not check runner-specific properties
838    /// since those are checked by the runner.
839    fn validate_program(
840        &mut self,
841        program: &fdecl::Program,
842        use_runner_name: Option<&String>,
843        _use_runner_source: Option<&fdecl::Ref>,
844    ) {
845        match &program.runner {
846            Some(_) =>
847            {
848                #[cfg(fuchsia_api_level_at_least = "HEAD")]
849                if use_runner_name.is_some() {
850                    if use_runner_name != program.runner.as_ref()
851                        || _use_runner_source
852                            != Some(&fdecl::Ref::Environment(fdecl::EnvironmentRef))
853                    {
854                        self.errors.push(Error::ConflictingRunners);
855                    }
856                }
857            }
858            None => {
859                if use_runner_name.is_none() {
860                    self.errors.push(Error::MissingRunner);
861                }
862            }
863        }
864
865        if program.info.is_none() {
866            self.errors.push(Error::missing_field(DeclType::Program, "info"));
867        }
868    }
869
870    /// Validates that paths-based capabilities (service, directory, protocol)
871    /// are different, are not prefixes of each other, and do not collide "/pkg".
872    fn validate_use_paths(&mut self, uses: &[fdecl::Use]) {
873        #[derive(Debug, PartialEq, Clone, Copy)]
874        struct PathCapability<'a> {
875            decl: DeclType,
876            dir: &'a Path,
877            use_: &'a fdecl::Use,
878        }
879        let mut used_paths = HashMap::new();
880        for use_ in uses.iter() {
881            match use_ {
882                fdecl::Use::Service(fdecl::UseService { target_path: Some(path), .. })
883                | fdecl::Use::Protocol(fdecl::UseProtocol { target_path: Some(path), .. })
884                | fdecl::Use::Directory(fdecl::UseDirectory { target_path: Some(path), .. })
885                | fdecl::Use::Storage(fdecl::UseStorage { target_path: Some(path), .. }) => {
886                    let capability = match use_ {
887                        fdecl::Use::Service(_) => {
888                            let dir = match Path::new(path).parent() {
889                                Some(p) => p,
890                                None => continue, // Invalid path, validated elsewhere
891                            };
892                            PathCapability { decl: DeclType::UseService, dir, use_ }
893                        }
894                        fdecl::Use::Protocol(_) => {
895                            let dir = match Path::new(path).parent() {
896                                Some(p) => p,
897                                None => continue, // Invalid path, validated elsewhere
898                            };
899                            PathCapability { decl: DeclType::UseProtocol, dir, use_ }
900                        }
901                        fdecl::Use::Directory(_) => PathCapability {
902                            decl: DeclType::UseDirectory,
903                            dir: Path::new(path),
904                            use_,
905                        },
906                        fdecl::Use::Storage(_) => PathCapability {
907                            decl: DeclType::UseStorage,
908                            dir: Path::new(path),
909                            use_,
910                        },
911                        _ => unreachable!(),
912                    };
913                    if used_paths.insert(path, capability).is_some() {
914                        // Disallow multiple capabilities for the same path.
915                        self.errors.push(Error::duplicate_field(
916                            capability.decl,
917                            "target_path",
918                            path,
919                        ));
920                    }
921                }
922                _ => {}
923            }
924        }
925        for ((&path_a, capability_a), (&path_b, capability_b)) in
926            used_paths.iter().tuple_combinations()
927        {
928            if match (capability_a.use_, capability_b.use_) {
929                // Directories and storage can't be the same or partially overlap.
930                (fdecl::Use::Directory(_), fdecl::Use::Directory(_))
931                | (fdecl::Use::Storage(_), fdecl::Use::Directory(_))
932                | (fdecl::Use::Directory(_), fdecl::Use::Storage(_))
933                | (fdecl::Use::Storage(_), fdecl::Use::Storage(_)) => {
934                    capability_b.dir == capability_a.dir
935                        || capability_b.dir.starts_with(capability_a.dir)
936                        || capability_a.dir.starts_with(capability_b.dir)
937                }
938
939                // Protocols and Services can't overlap with Directories.
940                (_, fdecl::Use::Directory(_)) | (fdecl::Use::Directory(_), _) => {
941                    capability_b.dir == capability_a.dir
942                        || capability_b.dir.starts_with(capability_a.dir)
943                        || capability_a.dir.starts_with(capability_b.dir)
944                }
945
946                // Protocols and Services containing directories may be same, but
947                // partial overlap is disallowed.
948                (_, _) => {
949                    capability_b.dir != capability_a.dir
950                        && (capability_b.dir.starts_with(capability_a.dir)
951                            || capability_a.dir.starts_with(capability_b.dir))
952                }
953            } {
954                self.errors.push(Error::invalid_path_overlap(
955                    capability_a.decl,
956                    path_a,
957                    capability_b.decl,
958                    path_b,
959                ));
960            }
961        }
962        for (used_path, capability) in used_paths.iter() {
963            if used_path.as_str() == "/pkg" || used_path.starts_with("/pkg/") {
964                self.errors.push(Error::pkg_path_overlap(capability.decl, *used_path));
965            }
966        }
967    }
968
969    fn validate_use_fields(
970        &mut self,
971        decl: DeclType,
972        // This takes a callback that returns a [Container], instead of the &[Container] directly,
973        // to avoid a borrow checker error that would occur from a simultaneous borrow on
974        // &mut self.
975        capability_checker: impl Fn(&Self) -> &dyn Container,
976        source: Option<&'a fdecl::Ref>,
977        source_name: Option<&'a String>,
978        source_dictionary: Option<&'a String>,
979        target_path: Option<&'a String>,
980        dependency_type: Option<&fdecl::DependencyType>,
981        availability: Option<&'a fdecl::Availability>,
982    ) {
983        self.validate_use_source(decl, source, source_dictionary);
984
985        check_name(source_name, decl, "source_name", &mut self.errors);
986        if source_dictionary.is_some() {
987            check_relative_path(source_dictionary, decl, "source_dictionary", &mut self.errors);
988        }
989        if decl != DeclType::UseRunner && decl != DeclType::UseConfiguration {
990            check_path(target_path, decl, "target_path", &mut self.errors);
991        }
992        check_use_availability(decl, availability, &mut self.errors);
993
994        // Only allow `weak` dependency with `use from child`.
995        let is_use_from_child = match source {
996            Some(fdecl::Ref::Child(_)) => true,
997            _ => false,
998        };
999        match (is_use_from_child, dependency_type) {
1000            (false, Some(fdecl::DependencyType::Weak)) => {
1001                self.errors.push(Error::invalid_field(decl, "dependency_type"));
1002            }
1003            _ => {}
1004        }
1005
1006        self.validate_route_from_self(
1007            decl,
1008            source,
1009            source_name,
1010            source_dictionary,
1011            capability_checker,
1012        );
1013    }
1014
1015    fn validate_use_source(
1016        &mut self,
1017        decl: DeclType,
1018        source: Option<&'a fdecl::Ref>,
1019        source_dictionary: Option<&'a String>,
1020    ) {
1021        match (source, source_dictionary) {
1022            // These sources support source_dictionary.
1023            (Some(fdecl::Ref::Parent(_)), _) => {}
1024            (Some(fdecl::Ref::Self_(_)), _) => {}
1025            (Some(fdecl::Ref::Child(child)), _) => {
1026                self.validate_child_ref(decl, "source", &child, OfferType::Static);
1027                return;
1028            }
1029            // These sources don't.
1030            (Some(fdecl::Ref::Framework(_)), None) => {}
1031            (Some(fdecl::Ref::Debug(_)), None) => {}
1032            (Some(fdecl::Ref::Capability(c)), None) => {
1033                self.validate_source_capability(&c, decl, "source");
1034                return;
1035            }
1036            #[cfg(fuchsia_api_level_at_least = "HEAD")]
1037            (Some(fdecl::Ref::Environment(_)), None) => {}
1038            (Some(fdecl::Ref::Collection(collection)), None) if decl == DeclType::UseService => {
1039                self.validate_collection_ref(decl, "source", &collection);
1040                return;
1041            }
1042            // `source` is required.
1043            (None, _) => self.errors.push(Error::missing_field(decl, "source")),
1044            // Any combination that was not recognized above must be invalid.
1045            (_, _) => self.errors.push(Error::invalid_field(decl, "source")),
1046        }
1047    }
1048
1049    fn validate_child_decl(&mut self, child: &'a fdecl::Child) {
1050        if let Err(mut e) = validate_child(child, check_name) {
1051            self.errors.append(&mut e.errs);
1052        }
1053        if let Some(name) = child.name.as_ref() {
1054            let name: &str = name;
1055            if self.all_children.insert(name, child).is_some() {
1056                self.errors.push(Error::duplicate_field(DeclType::Child, "name", name));
1057            }
1058        }
1059        if let Some(environment) = child.environment.as_ref() {
1060            if !self.all_environment_names.contains(environment.as_str()) {
1061                self.errors.push(Error::invalid_environment(
1062                    DeclType::Child,
1063                    "environment",
1064                    environment,
1065                ));
1066            }
1067        }
1068    }
1069
1070    fn validate_collection_decl(&mut self, collection: &'a fdecl::Collection) {
1071        let name = collection.name.as_ref();
1072        if check_name(name, DeclType::Collection, "name", &mut self.errors) {
1073            let name: &str = name.unwrap();
1074            if !self.all_collections.insert(name) {
1075                self.errors.push(Error::duplicate_field(DeclType::Collection, "name", name));
1076            }
1077        }
1078        if collection.durability.is_none() {
1079            self.errors.push(Error::missing_field(DeclType::Collection, "durability"));
1080        }
1081        if let Some(environment) = collection.environment.as_ref() {
1082            if !self.all_environment_names.contains(environment.as_str()) {
1083                self.errors.push(Error::invalid_environment(
1084                    DeclType::Collection,
1085                    "environment",
1086                    environment,
1087                ));
1088            }
1089        }
1090        // Allow `allowed_offers` & `allow_long_names` to be unset/unvalidated, for backwards compatibility.
1091    }
1092
1093    fn validate_environment_decl(&mut self, environment: &'a fdecl::Environment) {
1094        let name = environment.name.as_ref();
1095        check_name(name, DeclType::Environment, "name", &mut self.errors);
1096        if environment.extends.is_none() {
1097            self.errors.push(Error::missing_field(DeclType::Environment, "extends"));
1098        }
1099        if let Some(runners) = environment.runners.as_ref() {
1100            let mut registered_runners = HashSet::new();
1101            for runner in runners {
1102                self.validate_runner_registration(runner, &mut registered_runners);
1103            }
1104        }
1105        if let Some(resolvers) = environment.resolvers.as_ref() {
1106            let mut registered_schemes = HashSet::new();
1107            for resolver in resolvers {
1108                self.validate_resolver_registration(resolver, &mut registered_schemes);
1109            }
1110        }
1111
1112        match environment.extends.as_ref() {
1113            Some(fdecl::EnvironmentExtends::None) => {
1114                if environment.stop_timeout_ms.is_none() {
1115                    self.errors
1116                        .push(Error::missing_field(DeclType::Environment, "stop_timeout_ms"));
1117                }
1118            }
1119            None | Some(fdecl::EnvironmentExtends::Realm) => {}
1120        }
1121
1122        if let Some(debugs) = environment.debug_capabilities.as_ref() {
1123            for debug in debugs {
1124                self.validate_environment_debug_registration(debug);
1125            }
1126        }
1127    }
1128
1129    fn validate_runner_registration(
1130        &mut self,
1131        runner_registration: &'a fdecl::RunnerRegistration,
1132        runner_names: &mut HashSet<&'a str>,
1133    ) {
1134        check_name(
1135            runner_registration.source_name.as_ref(),
1136            DeclType::RunnerRegistration,
1137            "source_name",
1138            &mut self.errors,
1139        );
1140        self.validate_registration_source(
1141            runner_registration.source.as_ref(),
1142            DeclType::RunnerRegistration,
1143        );
1144        // If the source is `self`, ensure we have a corresponding Runner.
1145        if let (Some(fdecl::Ref::Self_(_)), Some(name)) =
1146            (&runner_registration.source, runner_registration.source_name.as_ref())
1147        {
1148            if !self.all_runners.contains(name as &str) {
1149                self.errors.push(Error::invalid_runner(
1150                    DeclType::RunnerRegistration,
1151                    "source_name",
1152                    name,
1153                ));
1154            }
1155        }
1156
1157        check_name(
1158            runner_registration.target_name.as_ref(),
1159            DeclType::RunnerRegistration,
1160            "target_name",
1161            &mut self.errors,
1162        );
1163        if let Some(name) = runner_registration.target_name.as_ref() {
1164            if !runner_names.insert(name.as_str()) {
1165                self.errors.push(Error::duplicate_field(
1166                    DeclType::RunnerRegistration,
1167                    "target_name",
1168                    name,
1169                ));
1170            }
1171        }
1172    }
1173
1174    fn validate_resolver_registration(
1175        &mut self,
1176        resolver_registration: &'a fdecl::ResolverRegistration,
1177        schemes: &mut HashSet<&'a str>,
1178    ) {
1179        check_name(
1180            resolver_registration.resolver.as_ref(),
1181            DeclType::ResolverRegistration,
1182            "resolver",
1183            &mut self.errors,
1184        );
1185        self.validate_registration_source(
1186            resolver_registration.source.as_ref(),
1187            DeclType::ResolverRegistration,
1188        );
1189        check_url_scheme(
1190            resolver_registration.scheme.as_ref(),
1191            DeclType::ResolverRegistration,
1192            "scheme",
1193            &mut self.errors,
1194        );
1195        if let Some(scheme) = resolver_registration.scheme.as_ref() {
1196            if !schemes.insert(scheme.as_str()) {
1197                self.errors.push(Error::duplicate_field(
1198                    DeclType::ResolverRegistration,
1199                    "scheme",
1200                    scheme,
1201                ));
1202            }
1203        }
1204    }
1205
1206    fn validate_registration_source(&mut self, source: Option<&'a fdecl::Ref>, ty: DeclType) {
1207        match source {
1208            Some(fdecl::Ref::Parent(_)) => {}
1209            Some(fdecl::Ref::Self_(_)) => {}
1210            Some(fdecl::Ref::Child(child_ref)) => {
1211                // Make sure the child is valid.
1212                self.validate_child_ref(ty, "source", &child_ref, OfferType::Static);
1213            }
1214            Some(_) => {
1215                self.errors.push(Error::invalid_field(ty, "source"));
1216            }
1217            None => {
1218                self.errors.push(Error::missing_field(ty, "source"));
1219            }
1220        }
1221    }
1222
1223    fn validate_service_decl(&mut self, service: &'a fdecl::Service, as_builtin: bool) {
1224        if check_name(service.name.as_ref(), DeclType::Service, "name", &mut self.errors) {
1225            let name = service.name.as_ref().unwrap();
1226            if !self.all_capability_ids.insert(name) {
1227                self.errors.push(Error::duplicate_field(DeclType::Service, "name", name.as_str()));
1228            }
1229            self.all_services.insert(name);
1230        }
1231        match as_builtin {
1232            true => {
1233                if let Some(path) = service.source_path.as_ref() {
1234                    self.errors.push(Error::extraneous_source_path(DeclType::Service, path))
1235                }
1236            }
1237            false => {
1238                check_path(
1239                    service.source_path.as_ref(),
1240                    DeclType::Service,
1241                    "source_path",
1242                    &mut self.errors,
1243                );
1244            }
1245        }
1246    }
1247
1248    fn validate_protocol_decl(&mut self, protocol: &'a fdecl::Protocol, as_builtin: bool) {
1249        if check_name(protocol.name.as_ref(), DeclType::Protocol, "name", &mut self.errors) {
1250            let name = protocol.name.as_ref().unwrap();
1251            if !self.all_capability_ids.insert(name) {
1252                self.errors.push(Error::duplicate_field(DeclType::Protocol, "name", name.as_str()));
1253            }
1254            self.all_protocols.insert(name);
1255        }
1256        match as_builtin {
1257            true => {
1258                if let Some(path) = protocol.source_path.as_ref() {
1259                    self.errors.push(Error::extraneous_source_path(DeclType::Protocol, path))
1260                }
1261            }
1262            false => {
1263                check_path(
1264                    protocol.source_path.as_ref(),
1265                    DeclType::Protocol,
1266                    "source_path",
1267                    &mut self.errors,
1268                );
1269            }
1270        }
1271
1272        #[cfg(fuchsia_api_level_at_least = "HEAD")]
1273        match protocol.delivery {
1274            Some(delivery) => match cm_types::DeliveryType::try_from(delivery) {
1275                Ok(_) => {}
1276                Err(_) => self.errors.push(Error::invalid_field(DeclType::Protocol, "delivery")),
1277            },
1278            None => {}
1279        }
1280    }
1281
1282    fn validate_directory_decl(&mut self, directory: &'a fdecl::Directory, as_builtin: bool) {
1283        if check_name(directory.name.as_ref(), DeclType::Directory, "name", &mut self.errors) {
1284            let name = directory.name.as_ref().unwrap();
1285            if !self.all_capability_ids.insert(name) {
1286                self.errors.push(Error::duplicate_field(
1287                    DeclType::Directory,
1288                    "name",
1289                    name.as_str(),
1290                ));
1291            }
1292            self.all_directories.insert(name);
1293        }
1294        match as_builtin {
1295            true => {
1296                if let Some(path) = directory.source_path.as_ref() {
1297                    self.errors.push(Error::extraneous_source_path(DeclType::Directory, path))
1298                }
1299            }
1300            false => {
1301                check_path(
1302                    directory.source_path.as_ref(),
1303                    DeclType::Directory,
1304                    "source_path",
1305                    &mut self.errors,
1306                );
1307            }
1308        }
1309        if directory.rights.is_none() {
1310            self.errors.push(Error::missing_field(DeclType::Directory, "rights"));
1311        }
1312    }
1313
1314    fn validate_storage_decl(&mut self, storage: &'a fdecl::Storage) {
1315        match storage.source.as_ref() {
1316            Some(fdecl::Ref::Parent(_)) => {}
1317            Some(fdecl::Ref::Self_(_)) => {}
1318            Some(fdecl::Ref::Child(child)) => {
1319                let _ =
1320                    self.validate_child_ref(DeclType::Storage, "source", &child, OfferType::Static);
1321            }
1322            Some(_) => {
1323                self.errors.push(Error::invalid_field(DeclType::Storage, "source"));
1324            }
1325            None => {
1326                self.errors.push(Error::missing_field(DeclType::Storage, "source"));
1327            }
1328        };
1329        if check_name(storage.name.as_ref(), DeclType::Storage, "name", &mut self.errors) {
1330            let name = storage.name.as_ref().unwrap();
1331            if !self.all_capability_ids.insert(name) {
1332                self.errors.push(Error::duplicate_field(DeclType::Storage, "name", name.as_str()));
1333            }
1334            self.all_storages.insert(name, storage.source.as_ref());
1335        }
1336        if storage.storage_id.is_none() {
1337            self.errors.push(Error::missing_field(DeclType::Storage, "storage_id"));
1338        }
1339        check_name(
1340            storage.backing_dir.as_ref(),
1341            DeclType::Storage,
1342            "backing_dir",
1343            &mut self.errors,
1344        );
1345    }
1346
1347    fn validate_runner_decl(&mut self, runner: &'a fdecl::Runner, as_builtin: bool) {
1348        if check_name(runner.name.as_ref(), DeclType::Runner, "name", &mut self.errors) {
1349            let name = runner.name.as_ref().unwrap();
1350            if !self.all_capability_ids.insert(name) {
1351                self.errors.push(Error::duplicate_field(DeclType::Runner, "name", name.as_str()));
1352            }
1353            self.all_runners.insert(name);
1354        }
1355        match as_builtin {
1356            true => {
1357                if let Some(path) = runner.source_path.as_ref() {
1358                    self.errors.push(Error::extraneous_source_path(DeclType::Runner, path))
1359                }
1360            }
1361            false => {
1362                check_path(
1363                    runner.source_path.as_ref(),
1364                    DeclType::Runner,
1365                    "source_path",
1366                    &mut self.errors,
1367                );
1368            }
1369        }
1370    }
1371
1372    fn validate_resolver_decl(&mut self, resolver: &'a fdecl::Resolver, as_builtin: bool) {
1373        if check_name(resolver.name.as_ref(), DeclType::Resolver, "name", &mut self.errors) {
1374            let name = resolver.name.as_ref().unwrap();
1375            if !self.all_capability_ids.insert(name) {
1376                self.errors.push(Error::duplicate_field(DeclType::Resolver, "name", name.as_str()));
1377            }
1378            self.all_resolvers.insert(name);
1379        }
1380        match as_builtin {
1381            true => {
1382                if let Some(path) = resolver.source_path.as_ref() {
1383                    self.errors.push(Error::extraneous_source_path(DeclType::Resolver, path))
1384                }
1385            }
1386            false => {
1387                check_path(
1388                    resolver.source_path.as_ref(),
1389                    DeclType::Resolver,
1390                    "source_path",
1391                    &mut self.errors,
1392                );
1393            }
1394        }
1395    }
1396
1397    // Dictionaries can reference other dictionaries in the same manifest, so before processing any
1398    // dictionary declarations this function should be called to do a first pass to pre-populate
1399    // the dictionary map.
1400    fn load_dictionary_names(&mut self, dictionaries: impl Iterator<Item = &'a fdecl::Dictionary>) {
1401        for dictionary in dictionaries {
1402            let decl = DeclType::Dictionary;
1403            if check_name(dictionary.name.as_ref(), decl, "name", &mut self.errors) {
1404                let name = dictionary.name.as_ref().unwrap();
1405                if !self.all_capability_ids.insert(name) {
1406                    self.errors.push(Error::duplicate_field(decl, "name", name.as_str()));
1407                }
1408                self.all_dictionaries.insert(name, &dictionary);
1409            }
1410        }
1411    }
1412
1413    fn validate_dictionary_decl(&mut self, dictionary: &'a fdecl::Dictionary) {
1414        let decl = DeclType::Dictionary;
1415        if let Some(path) = dictionary.source_path.as_ref() {
1416            if dictionary.source.is_some() {
1417                self.errors.push(Error::extraneous_field(decl, "source"));
1418            }
1419            check_path(Some(path), DeclType::Dictionary, "source_path", &mut self.errors);
1420        }
1421    }
1422
1423    #[cfg(fuchsia_api_level_at_least = "HEAD")]
1424    fn validate_configuration_decl(&mut self, config: &'a fdecl::Configuration) {
1425        let decl = DeclType::Configuration;
1426        if check_name(config.name.as_ref(), decl, "name", &mut self.errors) {
1427            let name = config.name.as_ref().unwrap();
1428            if !self.all_capability_ids.insert(name) {
1429                self.errors.push(Error::duplicate_field(decl, "name", name.as_str()));
1430            }
1431            self.all_configs.insert(name);
1432        }
1433    }
1434
1435    fn validate_environment_debug_registration(&mut self, debug: &'a fdecl::DebugRegistration) {
1436        match debug {
1437            fdecl::DebugRegistration::Protocol(o) => {
1438                let decl = DeclType::DebugProtocolRegistration;
1439                self.validate_environment_debug_fields(
1440                    decl,
1441                    o.source.as_ref(),
1442                    o.source_name.as_ref(),
1443                    o.target_name.as_ref(),
1444                );
1445
1446                if let (Some(fdecl::Ref::Self_(_)), Some(name)) =
1447                    (&o.source, o.source_name.as_ref())
1448                {
1449                    if !self.all_protocols.contains(&name as &str) {
1450                        self.errors.push(Error::invalid_field(decl, "source"));
1451                    }
1452                }
1453            }
1454            _ => {
1455                self.errors.push(Error::invalid_field(DeclType::Environment, "debug"));
1456            }
1457        }
1458    }
1459
1460    fn validate_environment_debug_fields(
1461        &mut self,
1462        decl: DeclType,
1463        source: Option<&fdecl::Ref>,
1464        source_name: Option<&String>,
1465        target_name: Option<&'a String>,
1466    ) {
1467        // We don't support "source" from "capability" for now.
1468        match source {
1469            Some(fdecl::Ref::Parent(_)) => {}
1470            Some(fdecl::Ref::Self_(_)) => {}
1471            Some(fdecl::Ref::Framework(_)) => {}
1472            Some(fdecl::Ref::Child(child)) => {
1473                let _ = self.validate_child_ref(decl, "source", &child, OfferType::Static);
1474            }
1475            Some(_) => self.errors.push(Error::invalid_field(decl, "source")),
1476            None => self.errors.push(Error::missing_field(decl, "source")),
1477        }
1478        check_name(source_name, decl, "source_name", &mut self.errors);
1479        check_name(target_name, decl, "target_name", &mut self.errors);
1480    }
1481
1482    fn validate_event_stream_decl(&mut self, event: &'a fdecl::EventStream) {
1483        if check_name(event.name.as_ref(), DeclType::EventStream, "name", &mut self.errors) {
1484            let name = event.name.as_ref().unwrap();
1485            if !self.all_capability_ids.insert(name) {
1486                self.errors.push(Error::duplicate_field(
1487                    DeclType::EventStream,
1488                    "name",
1489                    name.as_str(),
1490                ));
1491            }
1492        }
1493    }
1494
1495    fn validate_source_collection(
1496        &mut self,
1497        collection: &fdecl::CollectionRef,
1498        decl_type: DeclType,
1499    ) -> bool {
1500        let num_errors = self.errors.len();
1501        if check_name(Some(&collection.name), decl_type, "source.collection.name", &mut self.errors)
1502            && !self.all_collections.contains(&collection.name as &str)
1503        {
1504            self.errors.push(Error::invalid_collection(
1505                decl_type,
1506                "source",
1507                &collection.name as &str,
1508            ));
1509        }
1510        num_errors == self.errors.len()
1511    }
1512
1513    fn validate_filtered_service_fields(
1514        &mut self,
1515        decl_type: DeclType,
1516        source_instance_filter: Option<&Vec<String>>,
1517        renamed_instances: Option<&Vec<fdecl::NameMapping>>,
1518    ) {
1519        if let Some(source_instance_filter) = source_instance_filter {
1520            if source_instance_filter.is_empty() {
1521                // if the  source_instance_filter is empty the offered service will have 0 instances,
1522                // which means the offer shouldn't have been created at all.
1523                self.errors.push(Error::invalid_field(decl_type, "source_instance_filter"));
1524            }
1525            for name in source_instance_filter {
1526                check_name(Some(name), decl_type, "source_instance_filter", &mut self.errors);
1527            }
1528        }
1529        if let Some(renamed_instances) = renamed_instances {
1530            // Multiple sources shouldn't map to the same target name
1531            let mut seen_target_names = HashSet::<String>::new();
1532            for mapping in renamed_instances {
1533                check_name(
1534                    Some(&mapping.source_name),
1535                    decl_type,
1536                    "renamed_instances.source_name",
1537                    &mut self.errors,
1538                );
1539                check_name(
1540                    Some(&mapping.target_name),
1541                    decl_type,
1542                    "renamed_instances.target_name",
1543                    &mut self.errors,
1544                );
1545                if !seen_target_names.insert(mapping.target_name.clone()) {
1546                    self.errors.push(Error::invalid_field(decl_type, "renamed_instances"));
1547                    break;
1548                }
1549            }
1550        }
1551    }
1552
1553    fn validate_source_capability(
1554        &mut self,
1555        capability: &fdecl::CapabilityRef,
1556        decl_type: DeclType,
1557        field: &str,
1558    ) -> bool {
1559        let num_errors = self.errors.len();
1560        if check_name(Some(&capability.name), decl_type, "source.capability.name", &mut self.errors)
1561            && !self.all_capability_ids.contains(capability.name.as_str())
1562        {
1563            self.errors.push(Error::invalid_capability(decl_type, field, &capability.name));
1564        }
1565        num_errors == self.errors.len()
1566    }
1567
1568    /// Return a key that can be used in `HashMap` to group aggregate declarations.
1569    ///
1570    /// Returns `None` if the input resembles an invalid declaration.
1571    fn make_group_key(
1572        target_name: Option<&'a String>,
1573        target: Option<&'a fdecl::Ref>,
1574    ) -> Option<(&'a str, RefKey<'a>)> {
1575        if target_name.is_none() {
1576            return None;
1577        }
1578        let target_name = target_name.unwrap().as_str();
1579        if target.is_none() {
1580            return None;
1581        }
1582        let target = match target.unwrap() {
1583            fdecl::Ref::Parent(_) => RefKey::Parent,
1584            fdecl::Ref::Self_(_) => RefKey::Self_,
1585            fdecl::Ref::Child(r) => RefKey::Child(r.name.as_str()),
1586            fdecl::Ref::Collection(r) => RefKey::Collection(r.name.as_str()),
1587            fdecl::Ref::Framework(_) => RefKey::Framework,
1588            fdecl::Ref::Capability(_) => RefKey::Capability,
1589            fdecl::Ref::Debug(_) => RefKey::Debug,
1590            fdecl::RefUnknown!() => {
1591                return None;
1592            }
1593        };
1594        Some((target_name, target))
1595    }
1596
1597    fn validate_aggregation_has_same_availability(
1598        &mut self,
1599        route_group: &Vec<impl HasAvailability>,
1600    ) {
1601        // Use `BtreeSet` for stable ordering of items in error message.
1602        let availability_of_sources: BTreeSet<_> =
1603            route_group.iter().map(|r| r.availability()).collect();
1604
1605        // All sources that feed into an aggregation operation should have the same availability.
1606        if availability_of_sources.len() > 1 {
1607            self.errors.push(Error::different_availability_in_aggregation(
1608                availability_of_sources.into_iter().collect(),
1609            ));
1610        }
1611    }
1612
1613    // Checks a group of expose decls to confirm that any duplicate exposes are
1614    // valid aggregate expose declarations.
1615    fn validate_expose_group(&mut self, exposes: &'a Vec<fdecl::Expose>) {
1616        let mut expose_groups: HashMap<_, Vec<fdecl::ExposeService>> = HashMap::new();
1617        let service_exposes = exposes
1618            .into_iter()
1619            .filter_map(|o| if let fdecl::Expose::Service(s) = o { Some(s) } else { None });
1620        for expose in service_exposes {
1621            let key = Self::make_group_key(expose.target_name.as_ref(), expose.target.as_ref());
1622            if let Some(key) = key {
1623                expose_groups.entry(key).or_insert_with(|| vec![]).push(expose.clone());
1624            }
1625        }
1626        for expose_group in expose_groups.into_values() {
1627            if expose_group.len() == 1 {
1628                // If there are not multiple exposes for a (target_name, target) pair then there are
1629                // no aggregation conditions to check.
1630                continue;
1631            }
1632
1633            self.validate_aggregation_has_same_availability(&expose_group);
1634        }
1635    }
1636
1637    fn validate_expose_decl(
1638        &mut self,
1639        expose: &'a fdecl::Expose,
1640        expose_to_parent_ids: &mut HashMap<&'a str, AllowableIds>,
1641        expose_to_framework_ids: &mut HashMap<&'a str, AllowableIds>,
1642    ) {
1643        match expose {
1644            fdecl::Expose::Service(e) => {
1645                let decl = DeclType::ExposeService;
1646                self.validate_expose_fields(
1647                    decl,
1648                    AllowableIds::Many,
1649                    CollectionSource::Allow,
1650                    Self::service_checker,
1651                    e.source.as_ref(),
1652                    e.source_name.as_ref(),
1653                    e.source_dictionary.as_ref(),
1654                    e.target.as_ref(),
1655                    e.target_name.as_ref(),
1656                    e.availability.as_ref(),
1657                    expose_to_parent_ids,
1658                    expose_to_framework_ids,
1659                );
1660            }
1661            fdecl::Expose::Protocol(e) => {
1662                let decl = DeclType::ExposeProtocol;
1663                self.validate_expose_fields(
1664                    decl,
1665                    AllowableIds::One,
1666                    CollectionSource::Deny,
1667                    Self::protocol_checker,
1668                    e.source.as_ref(),
1669                    e.source_name.as_ref(),
1670                    e.source_dictionary.as_ref(),
1671                    e.target.as_ref(),
1672                    e.target_name.as_ref(),
1673                    e.availability.as_ref(),
1674                    expose_to_parent_ids,
1675                    expose_to_framework_ids,
1676                );
1677            }
1678            fdecl::Expose::Directory(e) => {
1679                let decl = DeclType::ExposeDirectory;
1680                self.validate_expose_fields(
1681                    decl,
1682                    AllowableIds::One,
1683                    CollectionSource::Deny,
1684                    Self::directory_checker,
1685                    e.source.as_ref(),
1686                    e.source_name.as_ref(),
1687                    e.source_dictionary.as_ref(),
1688                    e.target.as_ref(),
1689                    e.target_name.as_ref(),
1690                    e.availability.as_ref(),
1691                    expose_to_parent_ids,
1692                    expose_to_framework_ids,
1693                );
1694
1695                // Subdir makes sense when routing, but when exposing to framework the subdirectory
1696                // can be exposed directly.
1697                match e.target.as_ref() {
1698                    Some(fdecl::Ref::Framework(_)) => {
1699                        if e.subdir.is_some() {
1700                            self.errors.push(Error::invalid_field(decl, "subdir"));
1701                        }
1702                    }
1703                    _ => {}
1704                }
1705
1706                if let Some(subdir) = e.subdir.as_ref() {
1707                    check_relative_path(Some(subdir), decl, "subdir", &mut self.errors);
1708                }
1709            }
1710            fdecl::Expose::Runner(e) => {
1711                let decl = DeclType::ExposeRunner;
1712                self.validate_expose_fields(
1713                    decl,
1714                    AllowableIds::One,
1715                    CollectionSource::Deny,
1716                    Self::runner_checker,
1717                    e.source.as_ref(),
1718                    e.source_name.as_ref(),
1719                    e.source_dictionary.as_ref(),
1720                    e.target.as_ref(),
1721                    e.target_name.as_ref(),
1722                    Some(&fdecl::Availability::Required),
1723                    expose_to_parent_ids,
1724                    expose_to_framework_ids,
1725                );
1726            }
1727            fdecl::Expose::Resolver(e) => {
1728                let decl = DeclType::ExposeResolver;
1729                self.validate_expose_fields(
1730                    decl,
1731                    AllowableIds::One,
1732                    CollectionSource::Deny,
1733                    Self::resolver_checker,
1734                    e.source.as_ref(),
1735                    e.source_name.as_ref(),
1736                    e.source_dictionary.as_ref(),
1737                    e.target.as_ref(),
1738                    e.target_name.as_ref(),
1739                    Some(&fdecl::Availability::Required),
1740                    expose_to_parent_ids,
1741                    expose_to_framework_ids,
1742                );
1743            }
1744            fdecl::Expose::Dictionary(e) => {
1745                let decl = DeclType::ExposeDictionary;
1746                self.validate_expose_fields(
1747                    decl,
1748                    AllowableIds::One,
1749                    CollectionSource::Deny,
1750                    Self::dictionary_checker,
1751                    e.source.as_ref(),
1752                    e.source_name.as_ref(),
1753                    e.source_dictionary.as_ref(),
1754                    e.target.as_ref(),
1755                    e.target_name.as_ref(),
1756                    Some(&fdecl::Availability::Required),
1757                    expose_to_parent_ids,
1758                    expose_to_framework_ids,
1759                );
1760            }
1761            #[cfg(fuchsia_api_level_at_least = "HEAD")]
1762            fdecl::Expose::Config(e) => {
1763                let decl = DeclType::ExposeConfig;
1764                self.validate_expose_fields(
1765                    decl,
1766                    AllowableIds::One,
1767                    CollectionSource::Deny,
1768                    Self::config_checker,
1769                    e.source.as_ref(),
1770                    e.source_name.as_ref(),
1771                    None,
1772                    e.target.as_ref(),
1773                    e.target_name.as_ref(),
1774                    e.availability.as_ref(),
1775                    expose_to_parent_ids,
1776                    expose_to_framework_ids,
1777                );
1778            }
1779            _ => {
1780                self.errors.push(Error::invalid_field(DeclType::Component, "expose"));
1781            }
1782        }
1783    }
1784
1785    fn validate_expose_fields(
1786        &mut self,
1787        decl: DeclType,
1788        allowable_ids: AllowableIds,
1789        collection_source: CollectionSource,
1790        // This takes a callback that returns a [Container], instead of the &[Container] directly,
1791        // to avoid a borrow checker error that would occur from a simultaneous borrow on
1792        // &mut self.
1793        capability_checker: impl Fn(&Self) -> &dyn Container,
1794        source: Option<&fdecl::Ref>,
1795        source_name: Option<&String>,
1796        source_dictionary: Option<&String>,
1797        target: Option<&fdecl::Ref>,
1798        target_name: Option<&'a String>,
1799        availability: Option<&fdecl::Availability>,
1800        expose_to_parent_ids: &mut HashMap<&'a str, AllowableIds>,
1801        expose_to_framework_ids: &mut HashMap<&'a str, AllowableIds>,
1802    ) {
1803        self.validate_expose_source(decl, collection_source, source, source_dictionary);
1804        check_route_availability(decl, availability, source, source_name, &mut self.errors);
1805        match target {
1806            Some(r) => match r {
1807                fdecl::Ref::Parent(_) => {}
1808                fdecl::Ref::Framework(_) => {}
1809                _ => {
1810                    self.errors.push(Error::invalid_field(decl, "target"));
1811                }
1812            },
1813            None => {
1814                self.errors.push(Error::missing_field(decl, "target"));
1815            }
1816        }
1817        check_name(source_name, decl, "source_name", &mut self.errors);
1818        if source_dictionary.is_some() {
1819            check_relative_path(source_dictionary, decl, "source_dictionary", &mut self.errors);
1820        }
1821        if check_name(target_name, decl, "target_name", &mut self.errors) {
1822            let maybe_ids_set = match target {
1823                Some(fdecl::Ref::Parent(_)) => Some(expose_to_parent_ids),
1824                Some(fdecl::Ref::Framework(_)) => Some(expose_to_framework_ids),
1825                _ => None,
1826            };
1827            if let Some(ids_set) = maybe_ids_set {
1828                let target_name = target_name.unwrap();
1829                if let Some(prev_state) = ids_set.insert(target_name, allowable_ids) {
1830                    if prev_state == AllowableIds::One || prev_state != allowable_ids {
1831                        self.errors.push(Error::duplicate_field(decl, "target_name", target_name));
1832                    }
1833                }
1834            }
1835        }
1836
1837        self.validate_route_from_self(
1838            decl,
1839            source,
1840            source_name,
1841            source_dictionary,
1842            capability_checker,
1843        );
1844    }
1845
1846    fn validate_expose_source(
1847        &mut self,
1848        decl: DeclType,
1849        collection_source: CollectionSource,
1850        source: Option<&fdecl::Ref>,
1851        source_dictionary: Option<&String>,
1852    ) {
1853        match (source, source_dictionary) {
1854            // These sources support source_dictionary.
1855            (Some(fdecl::Ref::Self_(_)), _) => {}
1856            (Some(fdecl::Ref::Child(child)), _) => {
1857                let _ = self.validate_child_ref(decl, "source", &child, OfferType::Static);
1858            }
1859            // These sources don't.
1860            (Some(fdecl::Ref::VoidType(_)), None) => {}
1861            (Some(fdecl::Ref::Framework(_)), None) => {}
1862            (Some(fdecl::Ref::Capability(c)), None) => {
1863                self.validate_source_capability(c, decl, "source");
1864            }
1865            (Some(fdecl::Ref::Collection(c)), None)
1866                if collection_source == CollectionSource::Allow =>
1867            {
1868                self.validate_source_collection(c, decl);
1869            }
1870            // `source` is required.
1871            (None, _) => {
1872                self.errors.push(Error::missing_field(decl, "source"));
1873            }
1874            // Any combination that was not recognized above must be invalid.
1875            (_, _) => {
1876                self.errors.push(Error::invalid_field(decl, "source"));
1877            }
1878        }
1879    }
1880
1881    // Checks a group of offer decls to confirm that any duplicate offers are
1882    // valid aggregate offer declarations.
1883    fn validate_offer_group(&mut self, offers: &'a Vec<fdecl::Offer>, offer_type: OfferType) {
1884        let mut offer_groups: HashMap<_, Vec<fdecl::OfferService>> = HashMap::new();
1885        let service_offers = offers
1886            .into_iter()
1887            .filter_map(|o| if let fdecl::Offer::Service(s) = o { Some(s) } else { None });
1888        for offer in service_offers {
1889            let key = Self::make_group_key(offer.target_name.as_ref(), offer.target.as_ref());
1890            if let Some(key) = key {
1891                offer_groups.entry(key).or_insert_with(|| vec![]).push(offer.clone());
1892            }
1893        }
1894        for offer_group in offer_groups.into_values() {
1895            if offer_group.len() == 1 {
1896                // If there are not multiple offers for a (target_name, target) pair then there are
1897                // no aggregation conditions to check.
1898                continue;
1899            }
1900
1901            self.validate_aggregation_has_same_availability(&offer_group);
1902
1903            let mut source_instance_filter_entries: HashSet<String> = HashSet::new();
1904            let mut service_source_names: HashSet<String> = HashSet::new();
1905            for o in offer_group {
1906                // Currently only service capabilities can be aggregated
1907                match (o.source_instance_filter, offer_type) {
1908                    (Some(source_instance_filter), _) => {
1909                        for instance_name in source_instance_filter {
1910                            if !source_instance_filter_entries.insert(instance_name.clone()) {
1911                                // If the source instance in the filter has been seen before this
1912                                // means there is a conflicting aggregate service offer.
1913                                self.errors.push(Error::invalid_aggregate_offer(format!(
1914                                    "Conflicting source_instance_filter in aggregate service \
1915                                    offer, instance_name '{}' seen in filter lists multiple times",
1916                                    instance_name,
1917                                )));
1918                            }
1919                        }
1920                    }
1921                    (None, OfferType::Static) => {}
1922                    (None, OfferType::Dynamic) => {
1923                        // Dynamic offers must include a filter.
1924                        self.errors.push(Error::invalid_aggregate_offer(
1925                            "source_instance_filter must be set for dynamic aggregate service \
1926                            offers",
1927                        ));
1928                    }
1929                }
1930                service_source_names.insert(
1931                    o.source_name
1932                        .expect("Offer Service declarations must always contain source_name"),
1933                );
1934            }
1935
1936            if service_source_names.len() > 1 {
1937                self.errors.push(Error::invalid_aggregate_offer(format!(
1938                    "All aggregate service offers must have the same source_name, saw {}. Use \
1939                    renamed_instances to rename instance names to avoid conflict.",
1940                    service_source_names.into_iter().sorted().collect::<Vec<String>>().join(", ")
1941                )));
1942            }
1943        }
1944    }
1945
1946    fn validate_offer_decl(&mut self, offer: &'a fdecl::Offer, offer_type: OfferType) {
1947        match offer {
1948            fdecl::Offer::Service(o) => {
1949                let decl = DeclType::OfferService;
1950                self.validate_offer_fields(
1951                    decl,
1952                    AllowableIds::Many,
1953                    CollectionSource::Allow,
1954                    Self::service_checker,
1955                    o.source.as_ref(),
1956                    o.source_name.as_ref(),
1957                    o.source_dictionary.as_ref(),
1958                    o.target.as_ref(),
1959                    o.target_name.as_ref(),
1960                    #[cfg(fuchsia_api_level_at_least = "HEAD")]
1961                    Some(o.dependency_type.as_ref().unwrap_or(&fdecl::DependencyType::Strong)),
1962                    #[cfg(fuchsia_api_level_less_than = "HEAD")]
1963                    Some(&fdecl::DependencyType::Strong),
1964                    o.availability.as_ref(),
1965                    offer_type,
1966                );
1967                self.validate_filtered_service_fields(
1968                    decl,
1969                    o.source_instance_filter.as_ref(),
1970                    o.renamed_instances.as_ref(),
1971                );
1972            }
1973            fdecl::Offer::Protocol(o) => {
1974                let decl = DeclType::OfferProtocol;
1975                self.validate_offer_fields(
1976                    decl,
1977                    AllowableIds::One,
1978                    CollectionSource::Deny,
1979                    Self::protocol_checker,
1980                    o.source.as_ref(),
1981                    o.source_name.as_ref(),
1982                    o.source_dictionary.as_ref(),
1983                    o.target.as_ref(),
1984                    o.target_name.as_ref(),
1985                    o.dependency_type.as_ref(),
1986                    o.availability.as_ref(),
1987                    offer_type,
1988                );
1989            }
1990            fdecl::Offer::Directory(o) => {
1991                let decl = DeclType::OfferDirectory;
1992                self.validate_offer_fields(
1993                    decl,
1994                    AllowableIds::One,
1995                    CollectionSource::Deny,
1996                    Self::directory_checker,
1997                    o.source.as_ref(),
1998                    o.source_name.as_ref(),
1999                    o.source_dictionary.as_ref(),
2000                    o.target.as_ref(),
2001                    o.target_name.as_ref(),
2002                    o.dependency_type.as_ref(),
2003                    o.availability.as_ref(),
2004                    offer_type,
2005                );
2006                if let Some(subdir) = o.subdir.as_ref() {
2007                    check_relative_path(
2008                        Some(subdir),
2009                        DeclType::OfferDirectory,
2010                        "subdir",
2011                        &mut self.errors,
2012                    );
2013                }
2014            }
2015            fdecl::Offer::Storage(o) => {
2016                let decl = DeclType::OfferStorage;
2017                self.validate_storage_offer_fields(
2018                    decl,
2019                    Self::storage_checker,
2020                    o.source.as_ref(),
2021                    o.source_name.as_ref(),
2022                    o.target.as_ref(),
2023                    o.target_name.as_ref(),
2024                    o.availability.as_ref(),
2025                    offer_type,
2026                );
2027            }
2028            fdecl::Offer::Runner(o) => {
2029                let decl = DeclType::OfferRunner;
2030                self.validate_offer_fields(
2031                    decl,
2032                    AllowableIds::One,
2033                    CollectionSource::Deny,
2034                    Self::runner_checker,
2035                    o.source.as_ref(),
2036                    o.source_name.as_ref(),
2037                    o.source_dictionary.as_ref(),
2038                    o.target.as_ref(),
2039                    o.target_name.as_ref(),
2040                    Some(&fdecl::DependencyType::Strong),
2041                    Some(&fdecl::Availability::Required),
2042                    offer_type,
2043                );
2044            }
2045            fdecl::Offer::Resolver(o) => {
2046                let decl = DeclType::OfferResolver;
2047                self.validate_offer_fields(
2048                    decl,
2049                    AllowableIds::One,
2050                    CollectionSource::Deny,
2051                    Self::resolver_checker,
2052                    o.source.as_ref(),
2053                    o.source_name.as_ref(),
2054                    o.source_dictionary.as_ref(),
2055                    o.target.as_ref(),
2056                    o.target_name.as_ref(),
2057                    Some(&fdecl::DependencyType::Strong),
2058                    Some(&fdecl::Availability::Required),
2059                    offer_type,
2060                );
2061            }
2062            fdecl::Offer::EventStream(e) => {
2063                self.validate_event_stream_offer_fields(e, offer_type);
2064            }
2065            fdecl::Offer::Dictionary(o) => {
2066                let decl = DeclType::OfferDictionary;
2067                self.validate_offer_fields(
2068                    decl,
2069                    AllowableIds::One,
2070                    CollectionSource::Deny,
2071                    Self::dictionary_checker,
2072                    o.source.as_ref(),
2073                    o.source_name.as_ref(),
2074                    o.source_dictionary.as_ref(),
2075                    o.target.as_ref(),
2076                    o.target_name.as_ref(),
2077                    o.dependency_type.as_ref(),
2078                    o.availability.as_ref(),
2079                    offer_type,
2080                );
2081            }
2082            #[cfg(fuchsia_api_level_at_least = "HEAD")]
2083            fdecl::Offer::Config(o) => {
2084                let decl = DeclType::OfferConfig;
2085                self.validate_offer_fields(
2086                    decl,
2087                    AllowableIds::One,
2088                    CollectionSource::Deny,
2089                    Self::config_checker,
2090                    o.source.as_ref(),
2091                    o.source_name.as_ref(),
2092                    None,
2093                    o.target.as_ref(),
2094                    o.target_name.as_ref(),
2095                    Some(&fdecl::DependencyType::Strong),
2096                    o.availability.as_ref(),
2097                    offer_type,
2098                );
2099            }
2100            fdecl::OfferUnknown!() => {
2101                self.errors.push(Error::invalid_field(DeclType::Component, "offer"));
2102            }
2103        }
2104    }
2105
2106    fn validate_offer_fields(
2107        &mut self,
2108        decl: DeclType,
2109        allowable_names: AllowableIds,
2110        collection_source: CollectionSource,
2111        capability_checker: impl Fn(&Self) -> &dyn Container,
2112        source: Option<&'a fdecl::Ref>,
2113        source_name: Option<&'a String>,
2114        source_dictionary: Option<&'a String>,
2115        target: Option<&'a fdecl::Ref>,
2116        target_name: Option<&'a String>,
2117        dependency_type: Option<&'a fdecl::DependencyType>,
2118        availability: Option<&'a fdecl::Availability>,
2119        offer_type: OfferType,
2120    ) {
2121        self.validate_offer_source(decl, collection_source, source, source_dictionary, offer_type);
2122        check_route_availability(decl, availability, source, source_name, &mut self.errors);
2123        check_offer_name(source_name, decl, "source_name", offer_type, &mut self.errors);
2124        if source_dictionary.is_some() {
2125            check_relative_path(source_dictionary, decl, "source_dictionary", &mut self.errors);
2126        }
2127        self.validate_offer_target(decl, allowable_names, target, target_name, offer_type);
2128        check_offer_name(target_name, decl, "target_name", offer_type, &mut self.errors);
2129
2130        if dependency_type.is_none() {
2131            self.errors.push(Error::missing_field(decl, "dependency_type"));
2132        }
2133
2134        self.validate_route_from_self(
2135            decl,
2136            source,
2137            source_name,
2138            source_dictionary,
2139            capability_checker,
2140        );
2141    }
2142
2143    fn validate_offer_source(
2144        &mut self,
2145        decl: DeclType,
2146        collection_source: CollectionSource,
2147        source: Option<&'a fdecl::Ref>,
2148        source_dictionary: Option<&'a String>,
2149        offer_type: OfferType,
2150    ) {
2151        match (source, source_dictionary) {
2152            // These sources support source_dictionary.
2153            (Some(fdecl::Ref::Parent(_)), _) => {}
2154            (Some(fdecl::Ref::Self_(_)), _) => {}
2155            (Some(fdecl::Ref::Child(child)), _) => {
2156                self.validate_child_ref(decl, "source", &child, offer_type);
2157            }
2158            // These sources don't.
2159            (Some(fdecl::Ref::VoidType(_)), None) => {}
2160            (Some(fdecl::Ref::Framework(_)), None) => {}
2161            (Some(fdecl::Ref::Capability(c)), None) => {
2162                self.validate_source_capability(c, decl, "source");
2163            }
2164            (Some(fdecl::Ref::Collection(c)), None)
2165                if collection_source == CollectionSource::Allow =>
2166            {
2167                self.validate_source_collection(c, decl);
2168            }
2169            // `source` is required.
2170            (None, _) => self.errors.push(Error::missing_field(decl, "source")),
2171            // Any combination that was not recognized above must be invalid.
2172            (_, _) => self.errors.push(Error::invalid_field(decl, "source")),
2173        }
2174    }
2175
2176    fn validate_storage_offer_fields(
2177        &mut self,
2178        decl: DeclType,
2179        // This takes a callback that returns a [Container], instead of the &[Container] directly,
2180        // to avoid a borrow checker error that would occur from a simultaneous borrow on
2181        // &mut self.
2182        capability_checker: impl Fn(&Self) -> &dyn Container,
2183        source: Option<&'a fdecl::Ref>,
2184        source_name: Option<&'a String>,
2185        target: Option<&'a fdecl::Ref>,
2186        target_name: Option<&'a String>,
2187        availability: Option<&fdecl::Availability>,
2188        offer_type: OfferType,
2189    ) {
2190        match source {
2191            Some(fdecl::Ref::Parent(_) | fdecl::Ref::VoidType(_) | fdecl::Ref::Self_(_)) => {}
2192            Some(_) => {
2193                self.errors.push(Error::invalid_field(decl, "source"));
2194            }
2195            None => {
2196                self.errors.push(Error::missing_field(decl, "source"));
2197            }
2198        }
2199        check_route_availability(decl, availability, source, source_name, &mut self.errors);
2200        check_offer_name(source_name, decl, "source_name", offer_type, &mut self.errors);
2201        self.validate_offer_target(decl, AllowableIds::One, target, target_name, offer_type);
2202        check_offer_name(target_name, decl, "target_name", offer_type, &mut self.errors);
2203
2204        if let (Some(fdecl::Ref::Self_(_)), Some(name)) = (source, source_name) {
2205            if !(capability_checker)(self).contains(name) {
2206                self.errors.push(Error::invalid_capability(decl, "source", name));
2207            }
2208        }
2209    }
2210
2211    fn validate_event_stream_offer_fields(
2212        &mut self,
2213        event_stream: &'a fdecl::OfferEventStream,
2214        offer_type: OfferType,
2215    ) {
2216        let decl = DeclType::OfferEventStream;
2217        check_name(event_stream.source_name.as_ref(), decl, "source_name", &mut self.errors);
2218        if event_stream.target == Some(fdecl::Ref::Framework(fdecl::FrameworkRef {})) {
2219            // Expose to framework from framework is never valid.
2220            self.errors.push(Error::invalid_field(DeclType::OfferEventStream, "target"));
2221        }
2222        if let Some(scope) = &event_stream.scope {
2223            if scope.is_empty() {
2224                self.errors.push(Error::invalid_field(decl, "scope"));
2225            }
2226            for value in scope {
2227                match value {
2228                    fdecl::Ref::Child(child) => {
2229                        self.validate_child_ref(
2230                            DeclType::OfferEventStream,
2231                            "scope",
2232                            &child,
2233                            offer_type,
2234                        );
2235                    }
2236                    fdecl::Ref::Collection(collection) => {
2237                        self.validate_collection_ref(
2238                            DeclType::OfferEventStream,
2239                            "scope",
2240                            &collection,
2241                        );
2242                    }
2243                    _ => {
2244                        self.errors.push(Error::invalid_field(DeclType::OfferEventStream, "scope"));
2245                    }
2246                }
2247            }
2248        }
2249        // Only parent, framework, child, and void are valid.
2250        match event_stream.source {
2251            Some(
2252                fdecl::Ref::Parent(_)
2253                | fdecl::Ref::Framework(_)
2254                | fdecl::Ref::Child(_)
2255                | fdecl::Ref::VoidType(_),
2256            ) => {}
2257            Some(_) => {
2258                self.errors.push(Error::invalid_field(decl, "source"));
2259            }
2260            None => {
2261                self.errors.push(Error::missing_field(decl, "source"));
2262            }
2263        };
2264
2265        check_route_availability(
2266            decl,
2267            event_stream.availability.as_ref(),
2268            event_stream.source.as_ref(),
2269            event_stream.source_name.as_ref(),
2270            &mut self.errors,
2271        );
2272
2273        self.validate_offer_target(
2274            decl,
2275            AllowableIds::One,
2276            event_stream.target.as_ref(),
2277            event_stream.target_name.as_ref(),
2278            offer_type,
2279        );
2280        check_name(event_stream.target_name.as_ref(), decl, "target_name", &mut self.errors);
2281    }
2282
2283    /// Check a `ChildRef` contains a valid child that exists.
2284    fn validate_child_ref(
2285        &mut self,
2286        decl: DeclType,
2287        field_name: &str,
2288        child: &fdecl::ChildRef,
2289        offer_type: OfferType,
2290    ) -> bool {
2291        if offer_type == OfferType::Dynamic && child.collection.is_some() {
2292            return self.validate_dynamic_child_ref(decl, field_name, child);
2293        }
2294        // Ensure the name is valid, and the reference refers to a static child.
2295        //
2296        // We attempt to list all errors if possible.
2297        let mut valid = true;
2298        if !check_name(
2299            Some(&child.name),
2300            decl,
2301            &format!("{}.child.name", field_name),
2302            &mut self.errors,
2303        ) {
2304            valid = false;
2305        }
2306        if child.collection.is_some() {
2307            self.errors
2308                .push(Error::extraneous_field(decl, format!("{}.child.collection", field_name)));
2309            valid = false;
2310        }
2311        if !valid {
2312            return false;
2313        }
2314
2315        // Ensure the child exists.
2316        let name: &str = &child.name;
2317        if !self.all_children.contains_key(name) {
2318            self.errors.push(Error::invalid_child(decl, field_name, name));
2319            return false;
2320        }
2321
2322        true
2323    }
2324
2325    /// Check a `ChildRef` contains a valid dynamic child.
2326    ///
2327    /// The manifest we're validating doesn't contain dynamic children so we can't check if the dynamic
2328    /// child actually exists, but we can confirm things like the name is valid.
2329    fn validate_dynamic_child_ref(
2330        &mut self,
2331        decl: DeclType,
2332        field_name: &str,
2333        child: &fdecl::ChildRef,
2334    ) -> bool {
2335        // Ensure the name is valid.
2336        //
2337        // We attempt to list all errors if possible.
2338        let mut valid = true;
2339        if !check_dynamic_name(
2340            Some(&child.name),
2341            decl,
2342            &format!("{}.child.name", field_name),
2343            &mut self.errors,
2344        ) {
2345            valid = false;
2346        }
2347        if !check_name(
2348            child.collection.as_ref(),
2349            decl,
2350            &format!("{}.child.collection", field_name),
2351            &mut self.errors,
2352        ) {
2353            valid = false;
2354        }
2355        valid
2356    }
2357
2358    /// Check a `CollectionRef` is valid and refers to an existing collection.
2359    fn validate_collection_ref(
2360        &mut self,
2361        decl: DeclType,
2362        field_name: &str,
2363        collection: &fdecl::CollectionRef,
2364    ) -> bool {
2365        // Ensure the name is valid.
2366        if !check_name(
2367            Some(&collection.name),
2368            decl,
2369            &format!("{}.collection.name", field_name),
2370            &mut self.errors,
2371        ) {
2372            return false;
2373        }
2374
2375        // Ensure the collection exists.
2376        if !self.all_collections.contains(&collection.name as &str) {
2377            self.errors.push(Error::invalid_collection(decl, field_name, &collection.name as &str));
2378            return false;
2379        }
2380
2381        true
2382    }
2383
2384    fn validate_offer_target(
2385        &mut self,
2386        decl: DeclType,
2387        allowable_names: AllowableIds,
2388        target: Option<&'a fdecl::Ref>,
2389        target_name: Option<&'a String>,
2390        offer_type: OfferType,
2391    ) {
2392        match target {
2393            Some(fdecl::Ref::Child(c)) => {
2394                self.validate_target_child(decl, allowable_names, c, target_name, offer_type);
2395            }
2396            Some(fdecl::Ref::Collection(c)) => {
2397                self.validate_target_collection(decl, allowable_names, c, target_name);
2398            }
2399            Some(fdecl::Ref::Capability(c)) => {
2400                // Only offers to dictionary capabilities are valid.
2401                if let Some(d) = self.all_dictionaries.get(&c.name.as_str()) {
2402                    if d.source_path.is_some() {
2403                        // If `source_path` is present that means this is an offer into a
2404                        // dynamic dictionary, which is not allowed.
2405                        self.errors.push(Error::invalid_field(decl, "target"));
2406                    }
2407                } else {
2408                    self.errors.push(Error::invalid_field(decl, "target"));
2409                }
2410            }
2411            Some(_) => {
2412                self.errors.push(Error::invalid_field(decl, "target"));
2413            }
2414            None => {
2415                self.errors.push(Error::missing_field(decl, "target"));
2416            }
2417        }
2418    }
2419
2420    fn validate_target_child(
2421        &mut self,
2422        decl: DeclType,
2423        allowable_names: AllowableIds,
2424        child: &'a fdecl::ChildRef,
2425        target_name: Option<&'a String>,
2426        offer_type: OfferType,
2427    ) {
2428        if !self.validate_child_ref(decl, "target", child, offer_type) {
2429            return;
2430        }
2431        if let Some(target_name) = target_name {
2432            let names_for_target = self
2433                .target_ids
2434                .entry(TargetId::Component(
2435                    &child.name,
2436                    child.collection.as_ref().map(|s| s.as_str()),
2437                ))
2438                .or_insert(HashMap::new());
2439            if let Some(prev_state) = names_for_target.insert(target_name, allowable_names) {
2440                if prev_state == AllowableIds::One || prev_state != allowable_names {
2441                    self.errors.push(Error::duplicate_field(
2442                        decl,
2443                        "target_name",
2444                        target_name as &str,
2445                    ));
2446                }
2447            }
2448            if let Some(collection) = child.collection.as_ref() {
2449                if let Some(names_for_target) =
2450                    self.target_ids.get(&TargetId::Collection(&collection))
2451                {
2452                    if names_for_target.contains_key(&target_name.as_str()) {
2453                        // This dynamic offer conflicts with a static offer to the same collection.
2454                        self.errors.push(Error::duplicate_field(
2455                            decl,
2456                            "target_name",
2457                            target_name as &str,
2458                        ));
2459                    }
2460                }
2461            }
2462        }
2463    }
2464
2465    fn validate_target_collection(
2466        &mut self,
2467        decl: DeclType,
2468        allowable_names: AllowableIds,
2469        collection: &'a fdecl::CollectionRef,
2470        target_name: Option<&'a String>,
2471    ) {
2472        if !self.validate_collection_ref(decl, "target", &collection) {
2473            return;
2474        }
2475        if let Some(target_name) = target_name {
2476            let names_for_target = self
2477                .target_ids
2478                .entry(TargetId::Collection(&collection.name))
2479                .or_insert(HashMap::new());
2480            if let Some(prev_state) = names_for_target.insert(target_name, allowable_names) {
2481                if prev_state == AllowableIds::One || prev_state != allowable_names {
2482                    self.errors.push(Error::duplicate_field(
2483                        decl,
2484                        "target_name",
2485                        target_name as &str,
2486                    ));
2487                }
2488            }
2489        }
2490    }
2491
2492    fn validate_route_from_self(
2493        &mut self,
2494        decl: DeclType,
2495        source: Option<&fdecl::Ref>,
2496        source_name: Option<&String>,
2497        source_dictionary: Option<&String>,
2498        capability_checker: impl Fn(&Self) -> &dyn Container,
2499    ) {
2500        let (Some(fdecl::Ref::Self_(_)), Some(name)) = (source, source_name) else {
2501            return;
2502        };
2503        match source_dictionary {
2504            Some(source_dictionary) => {
2505                if let Ok(path) = cm_types::RelativePath::new(source_dictionary) {
2506                    if let Some(first_segment) = path.iter_segments().next().map(|s| s.as_str()) {
2507                        if !self.all_dictionaries.contains_key(first_segment) {
2508                            self.errors.push(Error::invalid_capability(
2509                                decl,
2510                                "source",
2511                                first_segment,
2512                            ));
2513                        }
2514                    }
2515                }
2516            }
2517            None => {
2518                if !(capability_checker)(self).contains(name) {
2519                    self.errors.push(Error::invalid_capability(decl, "source", name));
2520                }
2521            }
2522        }
2523    }
2524
2525    // The following functions can be used to convert a type-specific collection of capabilities
2526    // into [Container].
2527    fn service_checker(&self) -> &dyn Container {
2528        &self.all_services
2529    }
2530    fn protocol_checker(&self) -> &dyn Container {
2531        &self.all_protocols
2532    }
2533    fn directory_checker(&self) -> &dyn Container {
2534        &self.all_directories
2535    }
2536    fn runner_checker(&self) -> &dyn Container {
2537        &self.all_runners
2538    }
2539    fn resolver_checker(&self) -> &dyn Container {
2540        &self.all_resolvers
2541    }
2542
2543    fn dictionary_checker(&self) -> &dyn Container {
2544        &self.all_dictionaries
2545    }
2546
2547    #[cfg(fuchsia_api_level_at_least = "HEAD")]
2548    fn config_checker(&self) -> &dyn Container {
2549        &self.all_configs
2550    }
2551    fn storage_checker(&self) -> &dyn Container {
2552        &self.all_storages
2553    }
2554    fn event_stream_checker(&self) -> &dyn Container {
2555        // Components can't define their own event streams. If someone tries to route an event
2556        // stream from Self it should generate some other error. So just return `true` to bypass
2557        // the logic.
2558        struct AlwaysTrueContainer {}
2559        impl Container for AlwaysTrueContainer {
2560            fn contains(&self, _key: &str) -> bool {
2561                true
2562            }
2563        }
2564        static CONTAINER: AlwaysTrueContainer = AlwaysTrueContainer {};
2565        &CONTAINER
2566    }
2567}
2568
2569#[cfg(test)]
2570mod tests {
2571    use super::*;
2572    use cm_types::MAX_LONG_NAME_LENGTH;
2573    use test_case::test_case;
2574    use {
2575        fidl_fuchsia_component_decl as fdecl, fidl_fuchsia_data as fdata, fidl_fuchsia_io as fio,
2576    };
2577
2578    macro_rules! test_validate {
2579        (
2580            $(
2581                $test_name:ident => {
2582                    input = $input:expr,
2583                    result = $result:expr,
2584                },
2585            )+
2586        ) => {
2587            $(
2588                #[test]
2589                fn $test_name() {
2590                    validate_test($input, $result);
2591                }
2592            )+
2593        }
2594    }
2595
2596    macro_rules! test_validate_any_result {
2597        (
2598            $(
2599                $test_name:ident => {
2600                    input = $input:expr,
2601                    results = $results:expr,
2602                },
2603            )+
2604        ) => {
2605            $(
2606                #[test]
2607                fn $test_name() {
2608                    validate_test_any_result($input, $results);
2609                }
2610            )+
2611        }
2612    }
2613
2614    macro_rules! test_validate_values_data {
2615        (
2616            $(
2617                $test_name:ident => {
2618                    input = $input:expr,
2619                    result = $result:expr,
2620                },
2621            )+
2622        ) => {
2623            $(
2624                #[test]
2625                fn $test_name() {
2626                    validate_values_data_test($input, $result);
2627                }
2628            )+
2629        }
2630    }
2631
2632    macro_rules! test_validate_capabilities {
2633        (
2634            $(
2635                $test_name:ident => {
2636                    input = $input:expr,
2637                    as_builtin = $as_builtin:expr,
2638                    result = $result:expr,
2639                },
2640            )+
2641        ) => {
2642            $(
2643                #[test]
2644                fn $test_name() {
2645                    validate_capabilities_test($input, $as_builtin, $result);
2646                }
2647            )+
2648        }
2649    }
2650
2651    macro_rules! test_dependency {
2652        (
2653            $(
2654                ($test_name:ident) => {
2655                    ty = $ty:expr,
2656                    offer_decl = $offer_decl:expr,
2657                },
2658            )+
2659        ) => {
2660            $(
2661                #[test]
2662                fn $test_name() {
2663                    let mut decl = new_component_decl();
2664                    let dependencies = vec![
2665                        ("a", "b"),
2666                        ("b", "a"),
2667                    ];
2668                    let offers = dependencies.into_iter().map(|(from,to)| {
2669                        let mut offer_decl = $offer_decl;
2670                        offer_decl.source = Some(fdecl::Ref::Child(
2671                           fdecl::ChildRef { name: from.to_string(), collection: None },
2672                        ));
2673                        offer_decl.target = Some(fdecl::Ref::Child(
2674                           fdecl::ChildRef { name: to.to_string(), collection: None },
2675                        ));
2676                        $ty(offer_decl)
2677                    }).collect();
2678                    let children = ["a", "b"].iter().map(|name| {
2679                        fdecl::Child {
2680                            name: Some(name.to_string()),
2681                            url: Some(format!("fuchsia-pkg://fuchsia.com/pkg#meta/{}.cm", name)),
2682                            startup: Some(fdecl::StartupMode::Lazy),
2683                            on_terminate: None,
2684                            environment: None,
2685                            ..Default::default()
2686                        }
2687                    }).collect();
2688                    decl.offers = Some(offers);
2689                    decl.children = Some(children);
2690                    let result = Err(ErrorList::new(vec![
2691                        Error::dependency_cycle("{{child a -> child b -> child a}}")
2692                    ]));
2693                    validate_test(decl, result);
2694                }
2695            )+
2696        }
2697    }
2698
2699    macro_rules! test_weak_dependency {
2700        (
2701            $(
2702                ($test_name:ident) => {
2703                    ty = $ty:expr,
2704                    offer_decl = $offer_decl:expr,
2705                },
2706            )+
2707        ) => {
2708            $(
2709                #[test_case(fdecl::DependencyType::Weak)]
2710                fn $test_name(weak_dep: fdecl::DependencyType) {
2711                    let mut decl = new_component_decl();
2712                    let offers = vec![
2713                        {
2714                            let mut offer_decl = $offer_decl;
2715                            offer_decl.source = Some(fdecl::Ref::Child(
2716                               fdecl::ChildRef { name: "a".to_string(), collection: None },
2717                            ));
2718                            offer_decl.target = Some(fdecl::Ref::Child(
2719                               fdecl::ChildRef { name: "b".to_string(), collection: None },
2720                            ));
2721                            offer_decl.dependency_type = Some(fdecl::DependencyType::Strong);
2722                            $ty(offer_decl)
2723                        },
2724                        {
2725                            let mut offer_decl = $offer_decl;
2726                            offer_decl.source = Some(fdecl::Ref::Child(
2727                               fdecl::ChildRef { name: "b".to_string(), collection: None },
2728                            ));
2729                            offer_decl.target = Some(fdecl::Ref::Child(
2730                               fdecl::ChildRef { name: "a".to_string(), collection: None },
2731                            ));
2732                            offer_decl.dependency_type = Some(weak_dep);
2733                            $ty(offer_decl)
2734                        },
2735                    ];
2736                    let children = ["a", "b"].iter().map(|name| {
2737                        fdecl::Child {
2738                            name: Some(name.to_string()),
2739                            url: Some(format!("fuchsia-pkg://fuchsia.com/pkg#meta/{}.cm", name)),
2740                            startup: Some(fdecl::StartupMode::Lazy),
2741                            on_terminate: None,
2742                            environment: None,
2743                            ..Default::default()
2744                        }
2745                    }).collect();
2746                    decl.offers = Some(offers);
2747                    decl.children = Some(children);
2748                    let result = Ok(());
2749                    validate_test(decl, result);
2750                }
2751            )+
2752        }
2753    }
2754
2755    #[track_caller]
2756    fn validate_test(input: fdecl::Component, expected_res: Result<(), ErrorList>) {
2757        let res = validate(&input);
2758        assert_eq!(res, expected_res);
2759    }
2760
2761    #[track_caller]
2762    fn validate_test_any_result(input: fdecl::Component, expected_res: Vec<Result<(), ErrorList>>) {
2763        let res = format!("{:?}", validate(&input));
2764        let expected_res_debug = format!("{:?}", expected_res);
2765
2766        let matched_exp =
2767            expected_res.into_iter().find(|expected| res == format!("{:?}", expected));
2768
2769        assert!(
2770            matched_exp.is_some(),
2771            "assertion failed: Expected one of:\n{:?}\nActual:\n{:?}",
2772            expected_res_debug,
2773            res
2774        );
2775    }
2776
2777    #[track_caller]
2778    fn validate_values_data_test(
2779        input: fdecl::ConfigValuesData,
2780        expected_res: Result<(), ErrorList>,
2781    ) {
2782        let res = validate_values_data(&input);
2783        assert_eq!(res, expected_res);
2784    }
2785
2786    #[track_caller]
2787    fn validate_capabilities_test(
2788        input: Vec<fdecl::Capability>,
2789        as_builtin: bool,
2790        expected_res: Result<(), ErrorList>,
2791    ) {
2792        let res = validate_capabilities(&input, as_builtin);
2793        assert_eq!(res, expected_res);
2794    }
2795
2796    fn new_component_decl() -> fdecl::Component {
2797        fdecl::Component {
2798            program: None,
2799            uses: None,
2800            exposes: None,
2801            offers: None,
2802            facets: None,
2803            capabilities: None,
2804            children: None,
2805            collections: None,
2806            environments: None,
2807            ..Default::default()
2808        }
2809    }
2810
2811    test_validate_any_result! {
2812        test_validate_use_disallows_nested_dirs => {
2813            input = {
2814                let mut decl = new_component_decl();
2815                decl.uses = Some(vec![
2816                    fdecl::Use::Directory(fdecl::UseDirectory {
2817                        dependency_type: Some(fdecl::DependencyType::Strong),
2818                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2819                        source_name: Some("abc".to_string()),
2820                        target_path: Some("/foo/bar".to_string()),
2821                        rights: Some(fio::Operations::CONNECT),
2822                        subdir: None,
2823                        ..Default::default()
2824                    }),
2825                    fdecl::Use::Directory(fdecl::UseDirectory {
2826                        dependency_type: Some(fdecl::DependencyType::Strong),
2827                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2828                        source_name: Some("abc".to_string()),
2829                        target_path: Some("/foo/bar/baz".to_string()),
2830                        rights: Some(fio::Operations::CONNECT),
2831                        subdir: None,
2832                        ..Default::default()
2833                    }),
2834                ]);
2835                decl
2836            },
2837            results = vec![
2838                Err(ErrorList::new(vec![
2839                    Error::invalid_path_overlap(
2840                        DeclType::UseDirectory, "/foo/bar/baz", DeclType::UseDirectory, "/foo/bar"),
2841                ])),
2842                Err(ErrorList::new(vec![
2843                    Error::invalid_path_overlap(
2844                        DeclType::UseDirectory, "/foo/bar", DeclType::UseDirectory, "/foo/bar/baz"),
2845                ])),
2846            ],
2847        },
2848        test_validate_use_disallows_nested_dirs_storage => {
2849            input = {
2850                let mut decl = new_component_decl();
2851                decl.uses = Some(vec![
2852                    fdecl::Use::Storage(fdecl::UseStorage {
2853                        source_name: Some("abc".to_string()),
2854                        target_path: Some("/foo/bar".to_string()),
2855                        ..Default::default()
2856                    }),
2857                    fdecl::Use::Storage(fdecl::UseStorage {
2858                        source_name: Some("abc".to_string()),
2859                        target_path: Some("/foo/bar/baz".to_string()),
2860                        ..Default::default()
2861                    }),
2862                ]);
2863                decl
2864            },
2865            results = vec![
2866                Err(ErrorList::new(vec![
2867                    Error::invalid_path_overlap(
2868                        DeclType::UseStorage, "/foo/bar/baz", DeclType::UseStorage, "/foo/bar"),
2869                ])),
2870                Err(ErrorList::new(vec![
2871                    Error::invalid_path_overlap(
2872                        DeclType::UseStorage, "/foo/bar", DeclType::UseStorage, "/foo/bar/baz"),
2873                ])),
2874            ],
2875        },
2876        test_validate_use_disallows_nested_dirs_directory_and_storage => {
2877            input = {
2878                let mut decl = new_component_decl();
2879                decl.uses = Some(vec![
2880                    fdecl::Use::Directory(fdecl::UseDirectory {
2881                        dependency_type: Some(fdecl::DependencyType::Strong),
2882                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2883                        source_name: Some("abc".to_string()),
2884                        target_path: Some("/foo/bar".to_string()),
2885                        rights: Some(fio::Operations::CONNECT),
2886                        subdir: None,
2887                        ..Default::default()
2888                    }),
2889                    fdecl::Use::Storage(fdecl::UseStorage {
2890                        source_name: Some("abc".to_string()),
2891                        target_path: Some("/foo/bar/baz".to_string()),
2892                        ..Default::default()
2893                    }),
2894                ]);
2895                decl
2896            },
2897            results = vec![
2898                Err(ErrorList::new(vec![
2899                    Error::invalid_path_overlap(
2900                        DeclType::UseStorage, "/foo/bar/baz", DeclType::UseDirectory, "/foo/bar"),
2901                ])),
2902                Err(ErrorList::new(vec![
2903                    Error::invalid_path_overlap(
2904                        DeclType::UseDirectory, "/foo/bar", DeclType::UseStorage, "/foo/bar/baz"),
2905                ])),
2906            ],
2907        },
2908        test_validate_use_disallows_common_prefixes_protocol => {
2909            input = {
2910                let mut decl = new_component_decl();
2911                decl.uses = Some(vec![
2912                    fdecl::Use::Directory(fdecl::UseDirectory {
2913                        dependency_type: Some(fdecl::DependencyType::Strong),
2914                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2915                        source_name: Some("abc".to_string()),
2916                        target_path: Some("/foo/bar".to_string()),
2917                        rights: Some(fio::Operations::CONNECT),
2918                        subdir: None,
2919                        ..Default::default()
2920                    }),
2921                    fdecl::Use::Protocol(fdecl::UseProtocol {
2922                        dependency_type: Some(fdecl::DependencyType::Strong),
2923                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2924                        source_name: Some("crow".to_string()),
2925                        target_path: Some("/foo/bar/fuchsia.2".to_string()),
2926                        ..Default::default()
2927                    }),
2928                ]);
2929                decl
2930            },
2931            results = vec![
2932                Err(ErrorList::new(vec![
2933                    Error::invalid_path_overlap(
2934                        DeclType::UseProtocol, "/foo/bar/fuchsia.2", DeclType::UseDirectory, "/foo/bar"),
2935                ])),
2936                Err(ErrorList::new(vec![
2937                    Error::invalid_path_overlap(
2938                        DeclType::UseDirectory, "/foo/bar", DeclType::UseProtocol, "/foo/bar/fuchsia.2"),
2939                ])),
2940            ],
2941        },
2942        test_validate_use_disallows_common_prefixes_service => {
2943            input = {
2944                let mut decl = new_component_decl();
2945                decl.uses = Some(vec![
2946                    fdecl::Use::Directory(fdecl::UseDirectory {
2947                        dependency_type: Some(fdecl::DependencyType::Strong),
2948                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2949                        source_name: Some("abc".to_string()),
2950                        target_path: Some("/foo/bar".to_string()),
2951                        rights: Some(fio::Operations::CONNECT),
2952                        subdir: None,
2953                        ..Default::default()
2954                    }),
2955                    fdecl::Use::Service(fdecl::UseService {
2956                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2957                        source_name: Some("space".to_string()),
2958                        target_path: Some("/foo/bar/baz/fuchsia.logger.Log".to_string()),
2959                        dependency_type: Some(fdecl::DependencyType::Strong),
2960                        ..Default::default()
2961                    }),
2962                ]);
2963                decl
2964            },
2965            results = vec![
2966                Err(ErrorList::new(vec![
2967                    Error::invalid_path_overlap(
2968                        DeclType::UseService, "/foo/bar/baz/fuchsia.logger.Log", DeclType::UseDirectory, "/foo/bar"),
2969                ])),
2970                Err(ErrorList::new(vec![
2971                    Error::invalid_path_overlap(
2972                        DeclType::UseDirectory, "/foo/bar", DeclType::UseService, "/foo/bar/baz/fuchsia.logger.Log"),
2973                ])),
2974            ],
2975        },
2976        test_validate_use_disallows_pkg => {
2977            input = {
2978                let mut decl = new_component_decl();
2979                decl.uses = Some(vec![
2980                    fdecl::Use::Directory(fdecl::UseDirectory {
2981                        dependency_type: Some(fdecl::DependencyType::Strong),
2982                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2983                        source_name: Some("abc".to_string()),
2984                        target_path: Some("/pkg".to_string()),
2985                        rights: Some(fio::Operations::CONNECT),
2986                        subdir: None,
2987                        ..Default::default()
2988                    }),
2989                ]);
2990                decl
2991            },
2992            results = vec![
2993                Err(ErrorList::new(vec![
2994                    Error::pkg_path_overlap(DeclType::UseDirectory, "/pkg"),
2995                ])),
2996            ],
2997        },
2998        test_validate_use_disallows_pkg_overlap => {
2999            input = {
3000                let mut decl = new_component_decl();
3001                decl.uses = Some(vec![
3002                    fdecl::Use::Directory(fdecl::UseDirectory {
3003                        dependency_type: Some(fdecl::DependencyType::Strong),
3004                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3005                        source_name: Some("abc".to_string()),
3006                        target_path: Some("/pkg/foo".to_string()),
3007                        rights: Some(fio::Operations::CONNECT),
3008                        subdir: None,
3009                        ..Default::default()
3010                    }),
3011                ]);
3012                decl
3013            },
3014            results = vec![
3015                Err(ErrorList::new(vec![
3016                    Error::pkg_path_overlap(DeclType::UseDirectory, "/pkg/foo"),
3017                ])),
3018            ],
3019        },
3020        test_validate_use_optional_config_correct => {
3021            input = {
3022                let mut decl = new_component_decl();
3023                decl.uses = Some(vec![
3024                    fdecl::Use::Config(fdecl::UseConfiguration {
3025                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3026                        source_name: Some("abc".to_string()),
3027                        target_name: Some("foo".to_string()),
3028                        availability: Some(fdecl::Availability::Optional),
3029                        type_: Some(fdecl::ConfigType {
3030                            layout: fdecl::ConfigTypeLayout::Bool,
3031                            parameters: Some(Vec::new()),
3032                            constraints: Vec::new(),
3033                        }),
3034                        ..Default::default()
3035                    }),
3036                ]);
3037                decl.config = Some(fdecl::ConfigSchema {
3038                    fields: Some(vec![fdecl::ConfigField {
3039                        key: Some("foo".into()),
3040                        type_: Some(fdecl::ConfigType {
3041                            layout: fdecl::ConfigTypeLayout::Bool,
3042                            parameters: Some(Vec::new()),
3043                            constraints: Vec::new(),
3044                        }),
3045                        mutability: None,
3046                        ..Default::default()}]),
3047                    checksum: Some(fdecl::ConfigChecksum::Sha256([0;32])),
3048                    value_source: Some(fdecl::ConfigValueSource::PackagePath("path".into())),
3049                    ..Default::default()
3050                     });
3051                decl
3052            },
3053            results = vec![Ok(())],
3054        },
3055        test_validate_use_optional_config_no_config_schema => {
3056            input = {
3057                let mut decl = new_component_decl();
3058                decl.uses = Some(vec![
3059                    fdecl::Use::Config(fdecl::UseConfiguration {
3060                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3061                        source_name: Some("abc".to_string()),
3062                        target_name: Some("foo".to_string()),
3063                        availability: Some(fdecl::Availability::Optional),
3064                        type_: Some(fdecl::ConfigType {
3065                            layout: fdecl::ConfigTypeLayout::Bool,
3066                            parameters: None,
3067                            constraints: Vec::new(),
3068                        }),
3069                        ..Default::default()
3070                    }),
3071                ]);
3072                decl
3073            },
3074            results = vec![
3075                Err(ErrorList::new(vec![
3076                    Error::missing_field(DeclType::ConfigField, "config"),
3077                ])),
3078            ],
3079        },
3080        test_validate_use_optional_config_no_config_field => {
3081            input = {
3082                let mut decl = new_component_decl();
3083                decl.uses = Some(vec![
3084                    fdecl::Use::Config(fdecl::UseConfiguration {
3085                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3086                        source_name: Some("abc".to_string()),
3087                        target_name: Some("foo".to_string()),
3088                        availability: Some(fdecl::Availability::Optional),
3089                        type_: Some(fdecl::ConfigType {
3090                            layout: fdecl::ConfigTypeLayout::Bool,
3091                            parameters: None,
3092                            constraints: Vec::new(),
3093                        }),
3094                        ..Default::default()
3095                    }),
3096                ]);
3097                decl.config = Some(fdecl::ConfigSchema {
3098                    fields: Some(vec![]),
3099                    checksum: Some(fdecl::ConfigChecksum::Sha256([0;32])),
3100                    value_source: Some(fdecl::ConfigValueSource::PackagePath("path".into())),
3101                    ..Default::default()
3102                     });
3103                decl
3104            },
3105            results = vec![
3106                Err(ErrorList::new(vec![
3107                    Error::missing_field(DeclType::ConfigField, "foo"),
3108                ])),
3109            ],
3110        },
3111        test_validate_use_optional_config_bad_type => {
3112            input = {
3113                let mut decl = new_component_decl();
3114                decl.uses = Some(vec![
3115                    fdecl::Use::Config(fdecl::UseConfiguration {
3116                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3117                        source_name: Some("abc".to_string()),
3118                        target_name: Some("foo".to_string()),
3119                        availability: Some(fdecl::Availability::Optional),
3120                        type_: Some(fdecl::ConfigType {
3121                            layout: fdecl::ConfigTypeLayout::Bool,
3122                            parameters: None,
3123                            constraints: Vec::new(),
3124                        }),
3125                        ..Default::default()
3126                    }),
3127                ]);
3128                decl.config = Some(fdecl::ConfigSchema {
3129                    fields: Some(vec![fdecl::ConfigField {
3130                        key: Some("foo".into()),
3131                        type_: Some(fdecl::ConfigType {
3132                            layout: fdecl::ConfigTypeLayout::Int16,
3133                            parameters: Some(Vec::new()),
3134                            constraints: Vec::new(),
3135                        }),
3136                        mutability: None,
3137                        ..Default::default()}]),
3138                    checksum: Some(fdecl::ConfigChecksum::Sha256([0;32])),
3139                    value_source: Some(fdecl::ConfigValueSource::PackagePath("path".into())),
3140                    ..Default::default()
3141                     });
3142                decl
3143            },
3144            results = vec![
3145                Err(ErrorList::new(vec![
3146                    Error::invalid_field(DeclType::ConfigField, "foo"),
3147                ])),
3148            ],
3149        },
3150    }
3151
3152    test_validate_values_data! {
3153        test_values_data_ok => {
3154            input = fdecl::ConfigValuesData {
3155                values: Some(vec![
3156                    fdecl::ConfigValueSpec {
3157                        value: Some(fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Bool(true))),
3158                        ..Default::default()
3159                    }
3160                ]),
3161                checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])),
3162                ..Default::default()
3163            },
3164            result = Ok(()),
3165        },
3166        test_values_data_no_checksum => {
3167            input = fdecl::ConfigValuesData {
3168                values: Some(vec![]),
3169                checksum: None,
3170                ..Default::default()
3171            },
3172            result = Err(ErrorList::new(vec![
3173                Error::missing_field(DeclType::ConfigValuesData, "checksum")
3174            ])),
3175        },
3176        test_values_data_unknown_checksum => {
3177            input = fdecl::ConfigValuesData {
3178                values: Some(vec![]),
3179                checksum: Some(fdecl::ConfigChecksum::unknown_variant_for_testing()),
3180                ..Default::default()
3181            },
3182            result = Err(ErrorList::new(vec![
3183                Error::invalid_field(DeclType::ConfigValuesData, "checksum")
3184            ])),
3185        },
3186        test_values_data_no_values => {
3187            input = fdecl::ConfigValuesData {
3188                values: None,
3189                checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])),
3190                ..Default::default()
3191            },
3192            result = Err(ErrorList::new(vec![
3193                Error::missing_field(DeclType::ConfigValuesData, "values")
3194            ])),
3195        },
3196        test_values_data_no_inner_value => {
3197            input = fdecl::ConfigValuesData {
3198                values: Some(vec![
3199                    fdecl::ConfigValueSpec::default()
3200                ]),
3201                checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])),
3202                ..Default::default()
3203            },
3204            result = Err(ErrorList::new(vec![
3205                Error::missing_field(DeclType::ConfigValueSpec, "value")
3206            ])),
3207        },
3208        test_values_data_unknown_inner_value => {
3209            input = fdecl::ConfigValuesData {
3210                values: Some(vec![
3211                    fdecl::ConfigValueSpec {
3212                        value: Some(fdecl::ConfigValue::unknown_variant_for_testing()),
3213                        ..Default::default()
3214                    }
3215                ]),
3216                checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])),
3217                ..Default::default()
3218            },
3219            result = Err(ErrorList::new(vec![
3220                Error::invalid_field(DeclType::ConfigValueSpec, "value")
3221            ])),
3222        },
3223        test_values_data_unknown_single_value => {
3224            input = fdecl::ConfigValuesData {
3225                values: Some(vec![
3226                    fdecl::ConfigValueSpec {
3227                        value: Some(fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::unknown_variant_for_testing())),
3228                        ..Default::default()
3229                    }
3230                ]),
3231                checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])),
3232                ..Default::default()
3233            },
3234            result = Err(ErrorList::new(vec![
3235                Error::invalid_field(DeclType::ConfigValueSpec, "value")
3236            ])),
3237        },
3238        test_values_data_unknown_list_value => {
3239            input = fdecl::ConfigValuesData {
3240                values: Some(vec![
3241                    fdecl::ConfigValueSpec {
3242                        value: Some(fdecl::ConfigValue::Vector(fdecl::ConfigVectorValue::unknown_variant_for_testing())),
3243                        ..Default::default()
3244                    }
3245                ]),
3246                checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])),
3247                ..Default::default()
3248            },
3249            result = Err(ErrorList::new(vec![
3250                Error::invalid_field(DeclType::ConfigValueSpec, "value")
3251            ])),
3252        },
3253    }
3254
3255    test_validate! {
3256        // uses
3257        test_validate_uses_empty => {
3258            input = {
3259                let mut decl = new_component_decl();
3260                decl.program = Some(fdecl::Program {
3261                    runner: Some("elf".to_string()),
3262                    info: Some(fdata::Dictionary {
3263                        entries: None,
3264                        ..Default::default()
3265                    }),
3266                    ..Default::default()
3267                });
3268                decl.uses = Some(vec![
3269                    fdecl::Use::Service(fdecl::UseService {
3270                        source: None,
3271                        source_name: None,
3272                        target_path: None,
3273                        dependency_type: None,
3274                        ..Default::default()
3275                    }),
3276                    fdecl::Use::Protocol(fdecl::UseProtocol {
3277                        dependency_type: None,
3278                        source: None,
3279                        source_name: None,
3280                        target_path: None,
3281                        ..Default::default()
3282                    }),
3283                    fdecl::Use::Directory(fdecl::UseDirectory {
3284                        dependency_type: None,
3285                        source: None,
3286                        source_name: None,
3287                        target_path: None,
3288                        rights: None,
3289                        subdir: None,
3290                        ..Default::default()
3291                    }),
3292                    fdecl::Use::Storage(fdecl::UseStorage {
3293                        source_name: None,
3294                        target_path: None,
3295                        ..Default::default()
3296                    }),
3297                    fdecl::Use::EventStream(fdecl::UseEventStream {
3298                        source_name: None,
3299                        source: None,
3300                        target_path: None,
3301                        ..Default::default()
3302                    }),
3303                    fdecl::Use::Runner(fdecl::UseRunner {
3304                        source_name: None,
3305                        source: None,
3306                        ..Default::default()
3307                    }),
3308                ]);
3309                decl
3310            },
3311            result = Err(ErrorList::new(vec![
3312                Error::missing_field(DeclType::UseService, "source"),
3313                Error::missing_field(DeclType::UseService, "source_name"),
3314                Error::missing_field(DeclType::UseService, "target_path"),
3315                Error::missing_field(DeclType::UseService, "dependency_type"),
3316                Error::missing_field(DeclType::UseProtocol, "source"),
3317                Error::missing_field(DeclType::UseProtocol, "source_name"),
3318                Error::missing_field(DeclType::UseProtocol, "target_path"),
3319                Error::missing_field(DeclType::UseProtocol, "dependency_type"),
3320                Error::missing_field(DeclType::UseDirectory, "source"),
3321                Error::missing_field(DeclType::UseDirectory, "source_name"),
3322                Error::missing_field(DeclType::UseDirectory, "target_path"),
3323                Error::missing_field(DeclType::UseDirectory, "dependency_type"),
3324                Error::missing_field(DeclType::UseDirectory, "rights"),
3325                Error::missing_field(DeclType::UseStorage, "source_name"),
3326                Error::missing_field(DeclType::UseStorage, "target_path"),
3327                Error::missing_field(DeclType::UseEventStream, "source"),
3328                Error::missing_field(DeclType::UseEventStream, "source_name"),
3329                Error::missing_field(DeclType::UseEventStream, "target_path"),
3330                Error::missing_field(DeclType::UseRunner, "source"),
3331                Error::missing_field(DeclType::UseRunner, "source_name"),
3332            ])),
3333        },
3334        test_validate_missing_program_info => {
3335            input = {
3336                let mut decl = new_component_decl();
3337                decl.program = Some(fdecl::Program {
3338                    runner: Some("runner".to_string()),
3339                    info: None,
3340                    ..Default::default()
3341                });
3342                decl
3343            },
3344            result = Err(ErrorList::new(vec![
3345                Error::missing_field(DeclType::Program, "info")
3346            ])),
3347        },
3348        test_validate_uses_invalid_identifiers => {
3349            input = {
3350                let mut decl = new_component_decl();
3351                decl.uses = Some(vec![
3352                    fdecl::Use::Service(fdecl::UseService {
3353                        source: Some(fdecl::Ref::Capability(fdecl::CapabilityRef {
3354                            name: "^bad".to_string(),
3355                        })),
3356                        source_name: Some("foo/".to_string()),
3357                        target_path: Some("a/foo".to_string()),
3358                        dependency_type: Some(fdecl::DependencyType::Strong),
3359                        ..Default::default()
3360                    }),
3361                    fdecl::Use::Protocol(fdecl::UseProtocol {
3362                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3363                            name: "^bad".to_string(),
3364                            collection: None,
3365                        })),
3366                        source_name: Some("foo/".to_string()),
3367                        target_path: Some("b/foo".to_string()),
3368                        dependency_type: Some(fdecl::DependencyType::Strong),
3369                        ..Default::default()
3370                    }),
3371                    fdecl::Use::Directory(fdecl::UseDirectory {
3372                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3373                            name: "^bad".to_string(),
3374                            collection: None,
3375                        })),
3376                        source_name: Some("foo/".to_string()),
3377                        target_path: Some("c".to_string()),
3378                        rights: Some(fio::Operations::CONNECT),
3379                        subdir: Some("/foo".to_string()),
3380                        dependency_type: Some(fdecl::DependencyType::Strong),
3381                        ..Default::default()
3382                    }),
3383                    fdecl::Use::Storage(fdecl::UseStorage {
3384                        source_name: Some("foo/".to_string()),
3385                        target_path: Some("d".to_string()),
3386                        ..Default::default()
3387                    }),
3388                    fdecl::Use::EventStream(fdecl::UseEventStream {
3389                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3390                            name: "^bad".to_string(),
3391                            collection: None,
3392                        })),
3393                        source_name: Some("foo/".to_string()),
3394                        target_path: Some("e".to_string()),
3395                        ..Default::default()
3396                    }),
3397                    fdecl::Use::Runner(fdecl::UseRunner {
3398                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3399                            name: "^bad".to_string(),
3400                            collection: None,
3401                        })),
3402                        source_name: Some("foo/".to_string()),
3403                        ..Default::default()
3404                    }),
3405                ]);
3406                decl
3407            },
3408            result = Err(ErrorList::new(vec![
3409                Error::invalid_field(DeclType::UseService, "source.capability.name"),
3410                Error::invalid_field(DeclType::UseService, "source_name"),
3411                Error::invalid_field(DeclType::UseService, "target_path"),
3412                Error::invalid_field(DeclType::UseProtocol, "source.child.name"),
3413                Error::invalid_field(DeclType::UseProtocol, "source_name"),
3414                Error::invalid_field(DeclType::UseProtocol, "target_path"),
3415                Error::invalid_field(DeclType::UseDirectory, "source.child.name"),
3416                Error::invalid_field(DeclType::UseDirectory, "source_name"),
3417                Error::invalid_field(DeclType::UseDirectory, "target_path"),
3418                Error::invalid_field(DeclType::UseDirectory, "subdir"),
3419                Error::invalid_field(DeclType::UseStorage, "source_name"),
3420                Error::invalid_field(DeclType::UseStorage, "target_path"),
3421                Error::invalid_field(DeclType::UseEventStream, "source.child.name"),
3422                Error::invalid_field(DeclType::UseEventStream, "source_name"),
3423                Error::invalid_field(DeclType::UseEventStream, "target_path"),
3424                Error::invalid_field(DeclType::UseRunner, "source.child.name"),
3425                Error::invalid_field(DeclType::UseRunner, "source_name"),
3426            ])),
3427        },
3428        test_validate_uses_missing_source => {
3429            input = {
3430                fdecl::Component {
3431                    uses: Some(vec![
3432                        fdecl::Use::Protocol(fdecl::UseProtocol {
3433                            dependency_type: Some(fdecl::DependencyType::Strong),
3434                            source: Some(fdecl::Ref::Capability(fdecl::CapabilityRef {
3435                                name: "this-storage-doesnt-exist".to_string(),
3436                            })),
3437                            source_name: Some("fuchsia.sys2.StorageAdmin".to_string()),
3438                            target_path: Some("/svc/fuchsia.sys2.StorageAdmin".to_string()),
3439                            ..Default::default()
3440                        })
3441                    ]),
3442                    ..new_component_decl()
3443                }
3444            },
3445            result = Err(ErrorList::new(vec![
3446                Error::invalid_capability(DeclType::UseProtocol, "source", "this-storage-doesnt-exist"),
3447            ])),
3448        },
3449        test_validate_uses_invalid_child => {
3450            input = {
3451                fdecl::Component {
3452                    uses: Some(vec![
3453                        fdecl::Use::Protocol(fdecl::UseProtocol {
3454                            dependency_type: Some(fdecl::DependencyType::Strong),
3455                            source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "no-such-child".to_string(), collection: None})),
3456                            source_name: Some("fuchsia.sys2.StorageAdmin".to_string()),
3457                            target_path: Some("/svc/fuchsia.sys2.StorageAdmin".to_string()),
3458                            ..Default::default()
3459                        }),
3460                        fdecl::Use::Service(fdecl::UseService {
3461                            source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "no-such-child".to_string(), collection: None})),
3462                            source_name: Some("service_name".to_string()),
3463                            target_path: Some("/svc/service_name".to_string()),
3464                            dependency_type: Some(fdecl::DependencyType::Strong),
3465                            ..Default::default()
3466                        }),
3467                        fdecl::Use::Directory(fdecl::UseDirectory {
3468                            dependency_type: Some(fdecl::DependencyType::Strong),
3469                            source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "no-such-child".to_string(), collection: None})),
3470                            source_name: Some("DirectoryName".to_string()),
3471                            target_path: Some("/data/DirectoryName".to_string()),
3472                            rights: Some(fio::Operations::CONNECT),
3473                            subdir: None,
3474                            ..Default::default()
3475                        }),
3476                        fdecl::Use::Runner(fdecl::UseRunner {
3477                            source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "no-such-child".to_string(), collection: None})),
3478                            source_name: Some("RunnerName".to_string()),
3479                            ..Default::default()
3480                        }),
3481                    ]),
3482                    ..new_component_decl()
3483                }
3484            },
3485            result = Err(ErrorList::new(vec![
3486                Error::invalid_child(DeclType::UseProtocol, "source", "no-such-child"),
3487                Error::invalid_child(DeclType::UseService, "source", "no-such-child"),
3488                Error::invalid_child(DeclType::UseDirectory, "source", "no-such-child"),
3489                Error::invalid_child(DeclType::UseRunner, "source", "no-such-child"),
3490            ])),
3491        },
3492        test_validate_uses_invalid_capability_from_self => {
3493            input = {
3494                let mut decl = new_component_decl();
3495                decl.uses = Some(vec![
3496                    fdecl::Use::Service(fdecl::UseService {
3497                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
3498                        source_name: Some("fuchsia.some.library.SomeService".into()),
3499                        target_path: Some("/svc/foo".into()),
3500                        dependency_type: Some(fdecl::DependencyType::Strong),
3501                        ..Default::default()
3502                    }),
3503                    fdecl::Use::Protocol(fdecl::UseProtocol {
3504                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
3505                        source_name: Some("fuchsia.some.library.SomeProtocol".into()),
3506                        target_path: Some("/svc/bar".into()),
3507                        dependency_type: Some(fdecl::DependencyType::Strong),
3508                        ..Default::default()
3509                    }),
3510                    fdecl::Use::Directory(fdecl::UseDirectory {
3511                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
3512                        source_name: Some("dir".into()),
3513                        target_path: Some("/assets".into()),
3514                        dependency_type: Some(fdecl::DependencyType::Strong),
3515                        rights: Some(fio::Operations::CONNECT),
3516                        ..Default::default()
3517                    }),
3518                    fdecl::Use::Runner(fdecl::UseRunner {
3519                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
3520                        source_name: Some("source_elf".into()),
3521                        ..Default::default()
3522                    }),
3523                    fdecl::Use::Config(fdecl::UseConfiguration {
3524                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
3525                        source_name: Some("source_config".into()),
3526                        target_name: Some("config".into()),
3527                        type_: Some(fdecl::ConfigType {
3528                            layout: fdecl::ConfigTypeLayout::Bool,
3529                            parameters: Some(Vec::new()),
3530                            constraints: Vec::new(),
3531                        }),
3532                        ..Default::default()
3533                    }),
3534                    fdecl::Use::Protocol(fdecl::UseProtocol {
3535                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
3536                        source_name: Some("fuchsia.some.library.SomeProtocol".into()),
3537                        source_dictionary: Some("dict/inner".into()),
3538                        target_path: Some("/svc/baz".into()),
3539                        dependency_type: Some(fdecl::DependencyType::Strong),
3540                        ..Default::default()
3541                    }),
3542                ]);
3543                decl
3544            },
3545            result = Err(ErrorList::new(vec![
3546                Error::invalid_capability(
3547                    DeclType::UseService,
3548                    "source",
3549                    "fuchsia.some.library.SomeService"),
3550                Error::invalid_capability(
3551                    DeclType::UseProtocol,
3552                    "source",
3553                    "fuchsia.some.library.SomeProtocol"),
3554                Error::invalid_capability(DeclType::UseDirectory, "source", "dir"),
3555                Error::invalid_capability(DeclType::UseRunner, "source", "source_elf"),
3556                Error::invalid_capability(DeclType::UseConfiguration, "source", "source_config"),
3557                Error::invalid_capability(DeclType::UseProtocol, "source", "dict"),
3558            ])),
3559        },
3560        test_validate_use_from_child_offer_to_child_strong_cycle => {
3561            input = {
3562                fdecl::Component {
3563                    capabilities: Some(vec![
3564                        fdecl::Capability::Service(fdecl::Service {
3565                            name: Some("a".to_string()),
3566                            source_path: Some("/a".to_string()),
3567                            ..Default::default()
3568                        })]),
3569                    uses: Some(vec![
3570                        fdecl::Use::Protocol(fdecl::UseProtocol {
3571                            dependency_type: Some(fdecl::DependencyType::Strong),
3572                            source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "child".to_string(), collection: None})),
3573                            source_name: Some("fuchsia.sys2.StorageAdmin".to_string()),
3574                            target_path: Some("/svc/fuchsia.sys2.StorageAdmin".to_string()),
3575                            ..Default::default()
3576                        }),
3577                        fdecl::Use::Service(fdecl::UseService {
3578                            source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "child".to_string(), collection: None})),
3579                            source_name: Some("service_name".to_string()),
3580                            target_path: Some("/svc/service_name".to_string()),
3581                            dependency_type: Some(fdecl::DependencyType::Strong),
3582                            ..Default::default()
3583                        }),
3584                        fdecl::Use::Directory(fdecl::UseDirectory {
3585                            dependency_type: Some(fdecl::DependencyType::Strong),
3586                            source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "child".to_string(), collection: None})),
3587                            source_name: Some("DirectoryName".to_string()),
3588                            target_path: Some("/data/DirectoryName".to_string()),
3589                            rights: Some(fio::Operations::CONNECT),
3590                            subdir: None,
3591                            ..Default::default()
3592                        }),
3593                    ]),
3594                    offers: Some(vec![
3595                        fdecl::Offer::Service(fdecl::OfferService {
3596                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
3597                            source_name: Some("a".to_string()),
3598                            target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child".to_string(), collection: None })),
3599                            target_name: Some("a".to_string()),
3600                            ..Default::default()
3601                        })
3602                    ]),
3603                    children: Some(vec![
3604                        fdecl::Child {
3605                            name: Some("child".to_string()),
3606                            url: Some("fuchsia-pkg://fuchsia.com/foo".to_string()),
3607                            startup: Some(fdecl::StartupMode::Lazy),
3608                            on_terminate: None,
3609                            ..Default::default()
3610                        }
3611                    ]),
3612                    ..new_component_decl()
3613                }
3614            },
3615            result = Err(ErrorList::new(vec![
3616                Error::dependency_cycle("{{self -> child child -> self}}"),
3617            ])),
3618        },
3619        test_validate_use_from_child_storage_no_cycle => {
3620            input = {
3621                fdecl::Component {
3622                    capabilities: Some(vec![
3623                        fdecl::Capability::Storage(fdecl::Storage {
3624                            name: Some("cdata".to_string()),
3625                            source: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child2".to_string(), collection: None } )),
3626                            backing_dir: Some("minfs".to_string()),
3627                            storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker),
3628                            ..Default::default()
3629                        }),
3630                        fdecl::Capability::Storage(fdecl::Storage {
3631                            name: Some("pdata".to_string()),
3632                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
3633                            backing_dir: Some("minfs".to_string()),
3634                            storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker),
3635                            ..Default::default()
3636                        }),
3637                    ]),
3638                    uses: Some(vec![
3639                        fdecl::Use::Protocol(fdecl::UseProtocol {
3640                            dependency_type: Some(fdecl::DependencyType::Strong),
3641                            source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "child1".to_string(), collection: None})),
3642                            source_name: Some("a".to_string()),
3643                            target_path: Some("/svc/a".to_string()),
3644                            ..Default::default()
3645                        }),
3646                    ]),
3647                    offers: Some(vec![
3648                        fdecl::Offer::Storage(fdecl::OfferStorage {
3649                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
3650                            source_name: Some("cdata".to_string()),
3651                            target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child1".to_string(), collection: None })),
3652                            target_name: Some("cdata".to_string()),
3653                            ..Default::default()
3654                        }),
3655                        fdecl::Offer::Storage(fdecl::OfferStorage {
3656                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
3657                            source_name: Some("pdata".to_string()),
3658                            target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child1".to_string(), collection: None })),
3659                            target_name: Some("pdata".to_string()),
3660                            ..Default::default()
3661                        }),
3662                    ]),
3663                    children: Some(vec![
3664                        fdecl::Child {
3665                            name: Some("child1".to_string()),
3666                            url: Some("fuchsia-pkg://fuchsia.com/foo".to_string()),
3667                            startup: Some(fdecl::StartupMode::Lazy),
3668                            on_terminate: None,
3669                            ..Default::default()
3670                        },
3671                        fdecl::Child {
3672                            name: Some("child2".to_string()),
3673                            url: Some("fuchsia-pkg://fuchsia.com/foo2".to_string()),
3674                            startup: Some(fdecl::StartupMode::Lazy),
3675                            on_terminate: None,
3676                            ..Default::default()
3677                        }
3678                    ]),
3679                    ..new_component_decl()
3680                }
3681            },
3682            result = Ok(()),
3683        },
3684        test_validate_use_from_child_storage_cycle => {
3685            input = {
3686                fdecl::Component {
3687                    capabilities: Some(vec![
3688                        fdecl::Capability::Storage(fdecl::Storage {
3689                            name: Some("data".to_string()),
3690                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
3691                            backing_dir: Some("minfs".to_string()),
3692                            storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker),
3693                            ..Default::default()
3694                        }),
3695                    ]),
3696                    uses: Some(vec![
3697                        fdecl::Use::Protocol(fdecl::UseProtocol {
3698                            dependency_type: Some(fdecl::DependencyType::Strong),
3699                            source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "child".to_string(), collection: None})),
3700                            source_name: Some("a".to_string()),
3701                            target_path: Some("/svc/a".to_string()),
3702                            ..Default::default()
3703                        }),
3704                    ]),
3705                    offers: Some(vec![
3706                        fdecl::Offer::Storage(fdecl::OfferStorage {
3707                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
3708                            source_name: Some("data".to_string()),
3709                            target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child".to_string(), collection: None })),
3710                            target_name: Some("data".to_string()),
3711                            ..Default::default()
3712                        }),
3713                    ]),
3714                    children: Some(vec![
3715                        fdecl::Child {
3716                            name: Some("child".to_string()),
3717                            url: Some("fuchsia-pkg://fuchsia.com/foo".to_string()),
3718                            startup: Some(fdecl::StartupMode::Lazy),
3719                            on_terminate: None,
3720                            ..Default::default()
3721                        },
3722                    ]),
3723                    ..new_component_decl()
3724                }
3725            },
3726            result = Err(ErrorList::new(vec![
3727                Error::dependency_cycle("{{self -> capability data -> child child -> self}}"),
3728            ])),
3729        },
3730        test_validate_storage_strong_cycle_between_children => {
3731            input = {
3732                fdecl::Component {
3733                    capabilities: Some(vec![
3734                        fdecl::Capability::Storage(fdecl::Storage {
3735                            name: Some("data".to_string()),
3736                            source: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child1".to_string(), collection: None } )),
3737                            backing_dir: Some("minfs".to_string()),
3738                            storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker),
3739                            ..Default::default()
3740                        })
3741                    ]),
3742                    offers: Some(vec![
3743                        fdecl::Offer::Storage(fdecl::OfferStorage {
3744                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
3745                            source_name: Some("data".to_string()),
3746                            target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child2".to_string(), collection: None })),
3747                            target_name: Some("data".to_string()),
3748                            ..Default::default()
3749                        }),
3750                        fdecl::Offer::Service(fdecl::OfferService {
3751                            source: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child2".to_string(), collection: None })),
3752                            source_name: Some("a".to_string()),
3753                            target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child1".to_string(), collection: None })),
3754                            target_name: Some("a".to_string()),
3755                            ..Default::default()
3756                        }),
3757                    ]),
3758                    children: Some(vec![
3759                        fdecl::Child {
3760                            name: Some("child1".to_string()),
3761                            url: Some("fuchsia-pkg://fuchsia.com/foo".to_string()),
3762                            startup: Some(fdecl::StartupMode::Lazy),
3763                            on_terminate: None,
3764                            ..Default::default()
3765                        },
3766                        fdecl::Child {
3767                            name: Some("child2".to_string()),
3768                            url: Some("fuchsia-pkg://fuchsia.com/foo2".to_string()),
3769                            startup: Some(fdecl::StartupMode::Lazy),
3770                            on_terminate: None,
3771                            ..Default::default()
3772                        }
3773                    ]),
3774                    ..new_component_decl()
3775                }
3776            },
3777            result = Err(ErrorList::new(vec![
3778                Error::dependency_cycle("{{child child1 -> capability data -> child child2 -> child child1}}"),
3779            ])),
3780        },
3781        test_validate_strong_cycle_between_children_through_environment_debug => {
3782            input = {
3783                fdecl::Component {
3784                    environments: Some(vec![
3785                        fdecl::Environment {
3786                            name: Some("env".to_string()),
3787                            extends: Some(fdecl::EnvironmentExtends::Realm),
3788                            debug_capabilities: Some(vec![
3789                                fdecl::DebugRegistration::Protocol(fdecl::DebugProtocolRegistration {
3790                                    source: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child1".to_string(), collection: None })),
3791                                    source_name: Some("fuchsia.foo.Bar".to_string()),
3792                                    target_name: Some("fuchsia.foo.Bar".to_string()),
3793                                    ..Default::default()
3794                                }),
3795                            ]),
3796                            ..Default::default()
3797                        },
3798                    ]),
3799                    offers: Some(vec![
3800                        fdecl::Offer::Service(fdecl::OfferService {
3801                            source: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child2".to_string(), collection: None })),
3802                            source_name: Some("a".to_string()),
3803                            target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child1".to_string(), collection: None })),
3804                            target_name: Some("a".to_string()),
3805                            ..Default::default()
3806                        }),
3807                    ]),
3808                    children: Some(vec![
3809                        fdecl::Child {
3810                            name: Some("child1".to_string()),
3811                            url: Some("fuchsia-pkg://fuchsia.com/foo".to_string()),
3812                            startup: Some(fdecl::StartupMode::Lazy),
3813                            on_terminate: None,
3814                            ..Default::default()
3815                        },
3816                        fdecl::Child {
3817                            name: Some("child2".to_string()),
3818                            url: Some("fuchsia-pkg://fuchsia.com/foo2".to_string()),
3819                            startup: Some(fdecl::StartupMode::Lazy),
3820                            environment: Some("env".to_string()),
3821                            on_terminate: None,
3822                            ..Default::default()
3823                        }
3824                    ]),
3825                    ..new_component_decl()
3826                }
3827            },
3828            result = Err(ErrorList::new(vec![
3829                Error::dependency_cycle("{{child child1 -> environment env -> child child2 -> child child1}}"),
3830            ])),
3831        },
3832        test_validate_strong_cycle_between_children_through_environment_runner => {
3833            input = {
3834                fdecl::Component {
3835                    environments: Some(vec![
3836                        fdecl::Environment {
3837                            name: Some("env".to_string()),
3838                            extends: Some(fdecl::EnvironmentExtends::Realm),
3839                            runners: Some(vec![
3840                                fdecl::RunnerRegistration {
3841                                    source: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child1".to_string(), collection: None })),
3842                                    source_name: Some("coff".to_string()),
3843                                    target_name: Some("coff".to_string()),
3844                                    ..Default::default()
3845                                }
3846                            ]),
3847                            ..Default::default()
3848                        },
3849                    ]),
3850                    offers: Some(vec![
3851                        fdecl::Offer::Service(fdecl::OfferService {
3852                            source: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child2".to_string(), collection: None })),
3853                            source_name: Some("a".to_string()),
3854                            target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child1".to_string(), collection: None })),
3855                            target_name: Some("a".to_string()),
3856                            ..Default::default()
3857                        }),
3858                    ]),
3859                    children: Some(vec![
3860                        fdecl::Child {
3861                            name: Some("child1".to_string()),
3862                            url: Some("fuchsia-pkg://fuchsia.com/foo".to_string()),
3863                            startup: Some(fdecl::StartupMode::Lazy),
3864                            on_terminate: None,
3865                            ..Default::default()
3866                        },
3867                        fdecl::Child {
3868                            name: Some("child2".to_string()),
3869                            url: Some("fuchsia-pkg://fuchsia.com/foo2".to_string()),
3870                            startup: Some(fdecl::StartupMode::Lazy),
3871                            environment: Some("env".to_string()),
3872                            on_terminate: None,
3873                            ..Default::default()
3874                        }
3875                    ]),
3876                    ..new_component_decl()
3877                }
3878            },
3879            result = Err(ErrorList::new(vec![
3880                Error::dependency_cycle("{{child child1 -> environment env -> child child2 -> child child1}}"),
3881            ])),
3882        },
3883        test_validate_strong_cycle_between_children_through_environment_resolver => {
3884            input = {
3885                fdecl::Component {
3886                    environments: Some(vec![
3887                        fdecl::Environment {
3888                            name: Some("env".to_string()),
3889                            extends: Some(fdecl::EnvironmentExtends::Realm),
3890                            resolvers: Some(vec![
3891                                fdecl::ResolverRegistration {
3892                                    resolver: Some("gopher".to_string()),
3893                                    source: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child1".to_string(), collection: None })),
3894                                    scheme: Some("gopher".to_string()),
3895                                    ..Default::default()
3896                                }
3897                            ]),
3898                            ..Default::default()
3899                        },
3900                    ]),
3901                    offers: Some(vec![
3902                        fdecl::Offer::Service(fdecl::OfferService {
3903                            source: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child2".to_string(), collection: None })),
3904                            source_name: Some("a".to_string()),
3905                            target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child1".to_string(), collection: None })),
3906                            target_name: Some("a".to_string()),
3907                            ..Default::default()
3908                        }),
3909                    ]),
3910                    children: Some(vec![
3911                        fdecl::Child {
3912                            name: Some("child1".to_string()),
3913                            url: Some("fuchsia-pkg://fuchsia.com/foo".to_string()),
3914                            startup: Some(fdecl::StartupMode::Lazy),
3915                            on_terminate: None,
3916                            ..Default::default()
3917                        },
3918                        fdecl::Child {
3919                            name: Some("child2".to_string()),
3920                            url: Some("fuchsia-pkg://fuchsia.com/foo2".to_string()),
3921                            startup: Some(fdecl::StartupMode::Lazy),
3922                            environment: Some("env".to_string()),
3923                            on_terminate: None,
3924                            ..Default::default()
3925                        }
3926                    ]),
3927                    ..new_component_decl()
3928                }
3929            },
3930            result = Err(ErrorList::new(vec![
3931                Error::dependency_cycle("{{child child1 -> environment env -> child child2 -> child child1}}"),
3932            ])),
3933        },
3934        test_validate_strong_cycle_between_self_and_two_children => {
3935            input = {
3936                fdecl::Component {
3937                    capabilities: Some(vec![
3938                        fdecl::Capability::Protocol(fdecl::Protocol {
3939                            name: Some("fuchsia.foo.Bar".to_string()),
3940                            source_path: Some("/svc/fuchsia.foo.Bar".to_string()),
3941                            ..Default::default()
3942                        })
3943                    ]),
3944                    offers: Some(vec![
3945                        fdecl::Offer::Protocol(fdecl::OfferProtocol {
3946                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
3947                            source_name: Some("fuchsia.foo.Bar".to_string()),
3948                            target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child1".to_string(), collection: None })),
3949                            target_name: Some("fuchsia.foo.Bar".to_string()),
3950                            dependency_type: Some(fdecl::DependencyType::Strong),
3951                            ..Default::default()
3952                        }),
3953                        fdecl::Offer::Protocol(fdecl::OfferProtocol {
3954                            source: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child1".to_string(), collection: None })),
3955                            source_name: Some("fuchsia.bar.Baz".to_string()),
3956                            target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child2".to_string(), collection: None })),
3957                            target_name: Some("fuchsia.bar.Baz".to_string()),
3958                            dependency_type: Some(fdecl::DependencyType::Strong),
3959                            ..Default::default()
3960                        }),
3961                    ]),
3962                    uses: Some(vec![
3963                        fdecl::Use::Protocol(fdecl::UseProtocol {
3964                            source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "child2".to_string(), collection: None})),
3965                            source_name: Some("fuchsia.baz.Foo".to_string()),
3966                            target_path: Some("/svc/fuchsia.baz.Foo".to_string()),
3967                            dependency_type: Some(fdecl::DependencyType::Strong),
3968                            ..Default::default()
3969                        }),
3970                    ]),
3971                    children: Some(vec![
3972                        fdecl::Child {
3973                            name: Some("child1".to_string()),
3974                            url: Some("fuchsia-pkg://fuchsia.com/foo".to_string()),
3975                            startup: Some(fdecl::StartupMode::Lazy),
3976                            on_terminate: None,
3977                            ..Default::default()
3978                        },
3979                        fdecl::Child {
3980                            name: Some("child2".to_string()),
3981                            url: Some("fuchsia-pkg://fuchsia.com/foo2".to_string()),
3982                            startup: Some(fdecl::StartupMode::Lazy),
3983                            on_terminate: None,
3984                            ..Default::default()
3985                        }
3986                    ]),
3987                    ..new_component_decl()
3988                }
3989            },
3990            result = Err(ErrorList::new(vec![
3991                Error::dependency_cycle("{{self -> child child1 -> child child2 -> self}}"),
3992            ])),
3993        },
3994        test_validate_strong_cycle_with_self_storage => {
3995            input = {
3996                fdecl::Component {
3997                    capabilities: Some(vec![
3998                        fdecl::Capability::Storage(fdecl::Storage {
3999                            name: Some("data".to_string()),
4000                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
4001                            backing_dir: Some("minfs".to_string()),
4002                            storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker),
4003                            ..Default::default()
4004                        }),
4005                        fdecl::Capability::Directory(fdecl::Directory {
4006                            name: Some("minfs".to_string()),
4007                            source_path: Some("/minfs".to_string()),
4008                            rights: Some(fio::RW_STAR_DIR),
4009                            ..Default::default()
4010                        }),
4011                    ]),
4012                    offers: Some(vec![
4013                        fdecl::Offer::Storage(fdecl::OfferStorage {
4014                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
4015                            source_name: Some("data".to_string()),
4016                            target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child".to_string(), collection: None })),
4017                            target_name: Some("data".to_string()),
4018                            ..Default::default()
4019                        }),
4020                    ]),
4021                    uses: Some(vec![
4022                        fdecl::Use::Protocol(fdecl::UseProtocol {
4023                            dependency_type: Some(fdecl::DependencyType::Strong),
4024                            source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "child".to_string(), collection: None})),
4025                            source_name: Some("fuchsia.foo.Bar".to_string()),
4026                            target_path: Some("/svc/fuchsia.foo.Bar".to_string()),
4027                            ..Default::default()
4028                        }),
4029                    ]),
4030                    children: Some(vec![
4031                        fdecl::Child {
4032                            name: Some("child".to_string()),
4033                            url: Some("fuchsia-pkg://fuchsia.com/foo".to_string()),
4034                            startup: Some(fdecl::StartupMode::Lazy),
4035                            ..Default::default()
4036                        },
4037                    ]),
4038                    ..new_component_decl()
4039                }
4040            },
4041            result = Err(ErrorList::new(vec![
4042                Error::dependency_cycle("{{self -> capability data -> child child -> self}}"),
4043            ])),
4044        },
4045        test_validate_strong_cycle_with_self_storage_admin_protocol => {
4046            input = {
4047                fdecl::Component {
4048                    capabilities: Some(vec![
4049                        fdecl::Capability::Storage(fdecl::Storage {
4050                            name: Some("data".to_string()),
4051                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
4052                            backing_dir: Some("minfs".to_string()),
4053                            storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker),
4054                            ..Default::default()
4055                        }),
4056                        fdecl::Capability::Directory(fdecl::Directory {
4057                            name: Some("minfs".to_string()),
4058                            source_path: Some("/minfs".to_string()),
4059                            rights: Some(fio::RW_STAR_DIR),
4060                            ..Default::default()
4061                        }),
4062                    ]),
4063                    offers: Some(vec![
4064                        fdecl::Offer::Protocol(fdecl::OfferProtocol {
4065                            source: Some(fdecl::Ref::Capability(fdecl::CapabilityRef { name: "data".to_string() })),
4066                            source_name: Some("fuchsia.sys2.StorageAdmin".to_string()),
4067                            target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child".to_string(), collection: None })),
4068                            target_name: Some("fuchsia.sys2.StorageAdmin".to_string()),
4069                            dependency_type: Some(fdecl::DependencyType::Strong),
4070                            ..Default::default()
4071                        }),
4072                    ]),
4073                    uses: Some(vec![
4074                        fdecl::Use::Protocol(fdecl::UseProtocol {
4075                            dependency_type: Some(fdecl::DependencyType::Strong),
4076                            source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "child".to_string(), collection: None})),
4077                            source_name: Some("fuchsia.foo.Bar".to_string()),
4078                            target_path: Some("/svc/fuchsia.foo.Bar".to_string()),
4079                            ..Default::default()
4080                        }),
4081                    ]),
4082                    children: Some(vec![
4083                        fdecl::Child {
4084                            name: Some("child".to_string()),
4085                            url: Some("fuchsia-pkg://fuchsia.com/foo".to_string()),
4086                            startup: Some(fdecl::StartupMode::Lazy),
4087                            ..Default::default()
4088                        },
4089                    ]),
4090                    ..new_component_decl()
4091                }
4092            },
4093            result = Err(ErrorList::new(vec![
4094                Error::dependency_cycle("{{self -> capability data -> child child -> self}}"),
4095            ])),
4096        },
4097        test_validate_strong_cycle_with_dictionary => {
4098            input = fdecl::Component {
4099                offers: Some(vec![
4100                    fdecl::Offer::Dictionary(fdecl::OfferDictionary {
4101                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
4102                        source_name: Some("dict".into()),
4103                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4104                            name: "a".into(),
4105                            collection: None,
4106                        })),
4107                        target_name: Some("dict".into()),
4108                        dependency_type: Some(fdecl::DependencyType::Strong),
4109                        ..Default::default()
4110                    }),
4111                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
4112                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
4113                            name: "b".into(),
4114                            collection: None,
4115                        })),
4116                        source_name: Some("1".into()),
4117                        target: Some(fdecl::Ref::Capability(fdecl::CapabilityRef {
4118                            name: "dict".into(),
4119                        })),
4120                        target_name: Some("1".into()),
4121                        dependency_type: Some(fdecl::DependencyType::Strong),
4122                        ..Default::default()
4123                    }),
4124                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
4125                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
4126                            name: "a".into(),
4127                            collection: None,
4128                        })),
4129                        source_name: Some("2".into()),
4130                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4131                            name: "b".into(),
4132                            collection: None,
4133                        })),
4134                        target_name: Some("2".into()),
4135                        dependency_type: Some(fdecl::DependencyType::Strong),
4136                        ..Default::default()
4137                    }),
4138                ]),
4139                children: Some(vec![
4140                    fdecl::Child {
4141                        name: Some("a".into()),
4142                        url: Some("fuchsia-pkg://child".into()),
4143                        startup: Some(fdecl::StartupMode::Lazy),
4144                        ..Default::default()
4145                    },
4146                    fdecl::Child {
4147                        name: Some("b".into()),
4148                        url: Some("fuchsia-pkg://child".into()),
4149                        startup: Some(fdecl::StartupMode::Lazy),
4150                        ..Default::default()
4151                    },
4152                ]),
4153                capabilities: Some(vec![
4154                    fdecl::Capability::Dictionary(fdecl::Dictionary {
4155                        name: Some("dict".into()),
4156                        ..Default::default()
4157                    }),
4158                ]),
4159                ..Default::default()
4160            },
4161            result = Err(ErrorList::new(vec![
4162                Error::dependency_cycle("{{child a -> child b -> capability dict -> child a}}"),
4163            ])),
4164        },
4165        test_validate_strong_cycle_with_dictionary_indirect => {
4166            input = fdecl::Component {
4167                offers: Some(vec![
4168                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
4169                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
4170                        source_name: Some("3".into()),
4171                        source_dictionary: Some("dict".into()),
4172                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4173                            name: "a".into(),
4174                            collection: None,
4175                        })),
4176                        target_name: Some("3".into()),
4177                        dependency_type: Some(fdecl::DependencyType::Strong),
4178                        ..Default::default()
4179                    }),
4180                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
4181                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
4182                            name: "b".into(),
4183                            collection: None,
4184                        })),
4185                        source_name: Some("1".into()),
4186                        target: Some(fdecl::Ref::Capability(fdecl::CapabilityRef {
4187                            name: "dict".into(),
4188                        })),
4189                        target_name: Some("1".into()),
4190                        dependency_type: Some(fdecl::DependencyType::Strong),
4191                        ..Default::default()
4192                    }),
4193                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
4194                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
4195                            name: "a".into(),
4196                            collection: None,
4197                        })),
4198                        source_name: Some("2".into()),
4199                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4200                            name: "b".into(),
4201                            collection: None,
4202                        })),
4203                        target_name: Some("2".into()),
4204                        dependency_type: Some(fdecl::DependencyType::Strong),
4205                        ..Default::default()
4206                    }),
4207                ]),
4208                children: Some(vec![
4209                    fdecl::Child {
4210                        name: Some("a".into()),
4211                        url: Some("fuchsia-pkg://child".into()),
4212                        startup: Some(fdecl::StartupMode::Lazy),
4213                        ..Default::default()
4214                    },
4215                    fdecl::Child {
4216                        name: Some("b".into()),
4217                        url: Some("fuchsia-pkg://child".into()),
4218                        startup: Some(fdecl::StartupMode::Lazy),
4219                        ..Default::default()
4220                    },
4221                ]),
4222                capabilities: Some(vec![
4223                    fdecl::Capability::Dictionary(fdecl::Dictionary {
4224                        name: Some("dict".into()),
4225                        ..Default::default()
4226                    }),
4227                ]),
4228                ..Default::default()
4229            },
4230            result = Err(ErrorList::new(vec![
4231                Error::dependency_cycle("{{child a -> child b -> capability dict -> child a}}"),
4232            ])),
4233        },
4234        test_validate_use_dependency_cycle_with_dictionary => {
4235            input = fdecl::Component {
4236                offers: Some(vec![
4237                    fdecl::Offer::Dictionary(fdecl::OfferDictionary {
4238                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
4239                        source_name: Some("dict".into()),
4240                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4241                            name: "a".into(),
4242                            collection: None,
4243                        })),
4244                        target_name: Some("dict".into()),
4245                        dependency_type: Some(fdecl::DependencyType::Strong),
4246                        ..Default::default()
4247                    }),
4248                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
4249                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
4250                        source_name: Some("1".into()),
4251                        target: Some(fdecl::Ref::Capability(fdecl::CapabilityRef {
4252                            name: "dict".into(),
4253                        })),
4254                        target_name: Some("1".into()),
4255                        dependency_type: Some(fdecl::DependencyType::Strong),
4256                        ..Default::default()
4257                    }),
4258                ]),
4259                children: Some(vec![
4260                    fdecl::Child {
4261                        name: Some("a".into()),
4262                        url: Some("fuchsia-pkg://child".into()),
4263                        startup: Some(fdecl::StartupMode::Lazy),
4264                        ..Default::default()
4265                    },
4266                ]),
4267                uses: Some(vec![
4268                        fdecl::Use::Protocol(fdecl::UseProtocol {
4269                            dependency_type: Some(fdecl::DependencyType::Strong),
4270                            source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "a".to_string(), collection: None})),
4271                            source_name: Some("2".to_string()),
4272                            target_path: Some("/svc/foo".into()),
4273                            ..Default::default()
4274                        }),
4275                ]),
4276                capabilities: Some(vec![
4277                    fdecl::Capability::Dictionary(fdecl::Dictionary {
4278                        name: Some("dict".into()),
4279                        ..Default::default()
4280                    }),
4281                    fdecl::Capability::Protocol(fdecl::Protocol {
4282                        name: Some("1".to_string()),
4283                        source_path: Some("/path".to_string()),
4284                        ..Default::default()
4285                    }),
4286                ]),
4287                ..Default::default()
4288            },
4289            result = Err(ErrorList::new(vec![
4290                Error::dependency_cycle("{{self -> capability dict -> child a -> self}}"),
4291            ])),
4292        },
4293        test_validate_use_from_child_offer_to_child_weak_cycle => {
4294            input = {
4295                fdecl::Component {
4296                    capabilities: Some(vec![
4297                        fdecl::Capability::Service(fdecl::Service {
4298                            name: Some("a".to_string()),
4299                            source_path: Some("/a".to_string()),
4300                            ..Default::default()
4301                        })]),
4302                    uses: Some(vec![
4303                        fdecl::Use::Protocol(fdecl::UseProtocol {
4304                            dependency_type: Some(fdecl::DependencyType::Weak),
4305                            source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "child".to_string(), collection: None})),
4306                            source_name: Some("fuchsia.sys2.StorageAdmin".to_string()),
4307                            target_path: Some("/svc/fuchsia.sys2.StorageAdmin".to_string()),
4308                            ..Default::default()
4309                        }),
4310                        fdecl::Use::Service(fdecl::UseService {
4311                            source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "child".to_string(), collection: None})),
4312                            source_name: Some("service_name".to_string()),
4313                            target_path: Some("/svc/service_name".to_string()),
4314                            dependency_type: Some(fdecl::DependencyType::Weak),
4315                            ..Default::default()
4316                        }),
4317                        fdecl::Use::Directory(fdecl::UseDirectory {
4318                            dependency_type: Some(fdecl::DependencyType::Weak),
4319                            source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "child".to_string(), collection: None})),
4320                            source_name: Some("DirectoryName".to_string()),
4321                            target_path: Some("/data/DirectoryName".to_string()),
4322                            rights: Some(fio::Operations::CONNECT),
4323                            subdir: None,
4324                            ..Default::default()
4325                        }),
4326                    ]),
4327                    offers: Some(vec![
4328                        fdecl::Offer::Service(fdecl::OfferService {
4329                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
4330                            source_name: Some("a".to_string()),
4331                            target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child".to_string(), collection: None })),
4332                            target_name: Some("a".to_string()),
4333                            ..Default::default()
4334                        })
4335                    ]),
4336                    children: Some(vec![
4337                        fdecl::Child {
4338                            name: Some("child".to_string()),
4339                            url: Some("fuchsia-pkg://fuchsia.com/foo".to_string()),
4340                            startup: Some(fdecl::StartupMode::Lazy),
4341                            on_terminate: None,
4342                            ..Default::default()
4343                        }
4344                    ]),
4345                    ..new_component_decl()
4346                }
4347            },
4348            result = Ok(()),
4349        },
4350        test_validate_expose_from_self_to_framework_and_parent => {
4351            input = {
4352                fdecl::Component {
4353                    capabilities: Some(vec![
4354                        fdecl::Capability::Protocol(fdecl::Protocol {
4355                            name: Some("a".to_string()),
4356                            source_path: Some("/a".to_string()),
4357                            ..Default::default()
4358                        }),
4359                    ]),
4360                    exposes: Some(vec![
4361                        fdecl::Expose::Protocol(fdecl::ExposeProtocol {
4362                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
4363                            source_name: Some("a".to_string()),
4364                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4365                            target_name: Some("a".to_string()),
4366                            ..Default::default()
4367                        }),
4368                        fdecl::Expose::Protocol(fdecl::ExposeProtocol {
4369                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
4370                            source_name: Some("a".to_string()),
4371                            target: Some(fdecl::Ref::Framework(fdecl::FrameworkRef {})),
4372                            target_name: Some("a".to_string()),
4373                            ..Default::default()
4374                        }),
4375                    ]),
4376                    ..new_component_decl()
4377                }
4378            },
4379            result = Ok(()),
4380        },
4381        test_validate_use_from_not_child_weak => {
4382            input = {
4383                fdecl::Component {
4384                    uses: Some(vec![
4385                        fdecl::Use::Protocol(fdecl::UseProtocol {
4386                            dependency_type: Some(fdecl::DependencyType::Weak),
4387                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
4388                            source_name: Some("fuchsia.sys2.StorageAdmin".to_string()),
4389                            target_path: Some("/svc/fuchsia.sys2.StorageAdmin".to_string()),
4390                            ..Default::default()
4391                        }),
4392                    ]),
4393                    ..new_component_decl()
4394                }
4395            },
4396            result = Err(ErrorList::new(vec![
4397                Error::invalid_field(DeclType::UseProtocol, "dependency_type"),
4398            ])),
4399        },
4400        test_validate_event_stream_offer_valid_decls => {
4401            input = {
4402                let mut decl = new_component_decl();
4403                decl.offers = Some(vec![
4404                    fdecl::Offer::EventStream(fdecl::OfferEventStream {
4405                        source_name: Some("stopped".to_string()),
4406                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
4407                        target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})),
4408                        target_name: Some("stopped".to_string()),
4409                        ..Default::default()
4410                    }),
4411                    fdecl::Offer::EventStream(fdecl::OfferEventStream {
4412                        source_name: Some("started".to_string()),
4413                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
4414                        target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})),
4415                        target_name: Some("started".to_string()),
4416                        ..Default::default()
4417                    }),
4418                ]);
4419                decl.children = Some(vec![fdecl::Child{
4420                    name: Some("test".to_string()),
4421                    url: Some("fuchsia-pkg://fuchsia.com/logger#meta/logger.cm".to_string()),
4422                    startup: Some(fdecl::StartupMode::Lazy),
4423                    on_terminate: None,
4424                    environment: None,
4425                    ..Default::default()
4426                },
4427                fdecl::Child{
4428                    name: Some("test2".to_string()),
4429                    url: Some("fuchsia-pkg://fuchsia.com/fake_component#meta/fake_component.cm".to_string()),
4430                    startup: Some(fdecl::StartupMode::Lazy),
4431                    on_terminate: None,
4432                    environment: None,
4433                    ..Default::default()
4434                }
4435                ]);
4436                decl
4437            },
4438            result = Ok(()),
4439        },
4440        test_validate_event_stream_offer_to_framework_invalid => {
4441            input = {
4442                let mut decl = new_component_decl();
4443                decl.offers = Some(vec![
4444                    fdecl::Offer::EventStream(fdecl::OfferEventStream {
4445                        source_name: Some("stopped".to_string()),
4446                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
4447                        target: Some(fdecl::Ref::Framework(fdecl::FrameworkRef{})),
4448                        target_name: Some("stopped".to_string()),
4449                        ..Default::default()
4450                    }),
4451                    fdecl::Offer::EventStream(fdecl::OfferEventStream {
4452                        source_name: Some("started".to_string()),
4453                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
4454                        target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})),
4455                        target_name: Some("started".to_string()),
4456                        ..Default::default()
4457                    }),
4458                ]);
4459                decl.children = Some(vec![fdecl::Child{
4460                    name: Some("test".to_string()),
4461                    url: Some("fuchsia-pkg://fuchsia.com/logger#meta/logger.cm".to_string()),
4462                    startup: Some(fdecl::StartupMode::Lazy),
4463                    on_terminate: None,
4464                    environment: None,
4465                    ..Default::default()
4466                },
4467                fdecl::Child{
4468                    name: Some("test2".to_string()),
4469                    url: Some("fuchsia-pkg://fuchsia.com/fake_component#meta/fake_component.cm".to_string()),
4470                    startup: Some(fdecl::StartupMode::Lazy),
4471                    on_terminate: None,
4472                    environment: None,
4473                    ..Default::default()
4474                }
4475                ]);
4476                decl
4477            },
4478            result = Err(ErrorList::new(vec![
4479                Error::InvalidField(DeclField { decl: DeclType::OfferEventStream, field: "target".to_string() }),
4480                Error::InvalidField(DeclField { decl: DeclType::OfferEventStream, field: "target".to_string() }),
4481            ])),
4482        },
4483        test_validate_event_stream_offer_to_scope_zero_length_invalid => {
4484            input = {
4485                let mut decl = new_component_decl();
4486                decl.offers = Some(vec![
4487                    fdecl::Offer::EventStream(fdecl::OfferEventStream {
4488                        source_name: Some("started".to_string()),
4489                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
4490                        target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})),
4491                        scope: Some(vec![]),
4492                        target_name: Some("started".to_string()),
4493                        ..Default::default()
4494                    }),
4495                ]);
4496                decl.children = Some(vec![fdecl::Child{
4497                    name: Some("test".to_string()),
4498                    url: Some("fuchsia-pkg://fuchsia.com/logger#meta/logger.cm".to_string()),
4499                    startup: Some(fdecl::StartupMode::Lazy),
4500                    on_terminate: None,
4501                    environment: None,
4502                    ..Default::default()
4503                },
4504                fdecl::Child{
4505                    name: Some("test2".to_string()),
4506                    url: Some("fuchsia-pkg://fuchsia.com/fake_component#meta/fake_component.cm".to_string()),
4507                    startup: Some(fdecl::StartupMode::Lazy),
4508                    on_terminate: None,
4509                    environment: None,
4510                    ..Default::default()
4511                }
4512                ]);
4513                decl
4514            },
4515            result = Err(ErrorList::new(vec![
4516                Error::InvalidField(DeclField { decl: DeclType::OfferEventStream, field: "scope".to_string() }),
4517            ])),
4518        },
4519        test_validate_event_stream_offer_to_scope_framework_invalid => {
4520            input = {
4521                let mut decl = new_component_decl();
4522                decl.offers = Some(vec![
4523                    fdecl::Offer::EventStream(fdecl::OfferEventStream {
4524                        source_name: Some("started".to_string()),
4525                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
4526                        target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})),
4527                        scope: Some(vec![fdecl::Ref::Framework(fdecl::FrameworkRef{})]),
4528                        target_name: Some("started".to_string()),
4529                        ..Default::default()
4530                    }),
4531                ]);
4532                decl.children = Some(vec![fdecl::Child{
4533                    name: Some("test".to_string()),
4534                    url: Some("fuchsia-pkg://fuchsia.com/logger#meta/logger.cm".to_string()),
4535                    startup: Some(fdecl::StartupMode::Lazy),
4536                    on_terminate: None,
4537                    environment: None,
4538                    ..Default::default()
4539                },
4540                fdecl::Child{
4541                    name: Some("test2".to_string()),
4542                    url: Some("fuchsia-pkg://fuchsia.com/fake_component#meta/fake_component.cm".to_string()),
4543                    startup: Some(fdecl::StartupMode::Lazy),
4544                    on_terminate: None,
4545                    environment: None,
4546                    ..Default::default()
4547                }
4548                ]);
4549                decl
4550            },
4551            result = Err(ErrorList::new(vec![
4552                Error::InvalidField(DeclField { decl: DeclType::OfferEventStream, field: "scope".to_string() }),
4553            ])),
4554        },
4555        test_validate_event_stream_offer_to_scope_valid => {
4556            input = {
4557                let mut decl = new_component_decl();
4558                decl.offers = Some(vec![
4559                    fdecl::Offer::EventStream(fdecl::OfferEventStream {
4560                        source_name: Some("started".to_string()),
4561                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
4562                        target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})),
4563                        scope: Some(vec![fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})]),
4564                        target_name: Some("started".to_string()),
4565                        ..Default::default()
4566                    }),
4567                ]);
4568                decl.children = Some(vec![fdecl::Child{
4569                    name: Some("test".to_string()),
4570                    url: Some("fuchsia-pkg://fuchsia.com/logger#meta/logger.cm".to_string()),
4571                    startup: Some(fdecl::StartupMode::Lazy),
4572                    on_terminate: None,
4573                    environment: None,
4574                    ..Default::default()
4575                },
4576                fdecl::Child{
4577                    name: Some("test2".to_string()),
4578                    url: Some("fuchsia-pkg://fuchsia.com/fake_component#meta/fake_component.cm".to_string()),
4579                    startup: Some(fdecl::StartupMode::Lazy),
4580                    on_terminate: None,
4581                    environment: None,
4582                    ..Default::default()
4583                }
4584                ]);
4585                decl
4586            },
4587            result = Ok(()),
4588        },
4589        test_validate_event_stream_offer_to_scope_with_capability_requested => {
4590            input = {
4591                let mut decl = new_component_decl();
4592                decl.offers = Some(vec![
4593                    fdecl::Offer::EventStream(fdecl::OfferEventStream {
4594                        source_name: Some("capability_requested".to_string()),
4595                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
4596                        target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})),
4597                        scope: Some(vec![fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})]),
4598                        target_name: Some("started".to_string()),
4599                        ..Default::default()
4600                    }),
4601                ]);
4602                decl.children = Some(vec![fdecl::Child{
4603                    name: Some("test".to_string()),
4604                    url: Some("fuchsia-pkg://fuchsia.com/logger#meta/logger.cm".to_string()),
4605                    startup: Some(fdecl::StartupMode::Lazy),
4606                    on_terminate: None,
4607                    environment: None,
4608                    ..Default::default()
4609                },
4610                fdecl::Child{
4611                    name: Some("test2".to_string()),
4612                    url: Some("fuchsia-pkg://fuchsia.com/fake_component#meta/fake_component.cm".to_string()),
4613                    startup: Some(fdecl::StartupMode::Lazy),
4614                    on_terminate: None,
4615                    environment: None,
4616                    ..Default::default()
4617                }
4618                ]);
4619                decl
4620            },
4621            result = Ok(()),
4622        },
4623        test_validate_event_stream_offer_with_no_source_name_invalid => {
4624            input = {
4625                let mut decl = new_component_decl();
4626                decl.offers = Some(vec![
4627                    fdecl::Offer::EventStream(fdecl::OfferEventStream {
4628                        source_name: None,
4629                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
4630                        target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})),
4631                        scope: Some(vec![fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})]),
4632                        target_name: Some("started".to_string()),
4633                        ..Default::default()
4634                    }),
4635                ]);
4636                decl.children = Some(vec![fdecl::Child{
4637                    name: Some("test".to_string()),
4638                    url: Some("fuchsia-pkg://fuchsia.com/logger#meta/logger.cm".to_string()),
4639                    startup: Some(fdecl::StartupMode::Lazy),
4640                    on_terminate: None,
4641                    environment: None,
4642                    ..Default::default()
4643                },
4644                fdecl::Child{
4645                    name: Some("test2".to_string()),
4646                    url: Some("fuchsia-pkg://fuchsia.com/fake_component#meta/fake_component.cm".to_string()),
4647                    startup: Some(fdecl::StartupMode::Lazy),
4648                    on_terminate: None,
4649                    environment: None,
4650                    ..Default::default()
4651                }
4652                ]);
4653                decl
4654            },
4655            result = Err(ErrorList::new(vec![
4656                Error::MissingField(DeclField { decl: DeclType::OfferEventStream, field: "source_name".to_string() }),
4657            ])),
4658        },
4659        test_validate_event_stream_offer_invalid_source => {
4660            input = {
4661                let mut decl = new_component_decl();
4662                decl.offers = Some(vec![
4663                    fdecl::Offer::EventStream(fdecl::OfferEventStream {
4664                        source_name: Some("stopped".to_string()),
4665                        source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef{})),
4666                        target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})),
4667                        target_name: Some("stopped".to_string()),
4668                        ..Default::default()
4669                    }),
4670                    fdecl::Offer::EventStream(fdecl::OfferEventStream {
4671                        source_name: Some("started".to_string()),
4672                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
4673                        target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})),
4674                        target_name: Some("started".to_string()),
4675                        ..Default::default()
4676                    }),
4677                    fdecl::Offer::EventStream(fdecl::OfferEventStream {
4678                        source_name: Some("capability_requested".to_string()),
4679                        source: Some(fdecl::Ref::Debug(fdecl::DebugRef{})),
4680                        target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})),
4681                        target_name: Some("capability_requested".to_string()),
4682                        ..Default::default()
4683                    }),
4684                ]);
4685                decl.children = Some(vec![fdecl::Child{
4686                    name: Some("test".to_string()),
4687                    url: Some("fuchsia-pkg://fuchsia.com/logger#meta/logger.cm".to_string()),
4688                    startup: Some(fdecl::StartupMode::Lazy),
4689                    on_terminate: None,
4690                    environment: None,
4691                    ..Default::default()
4692                },
4693                fdecl::Child{
4694                    name: Some("test2".to_string()),
4695                    url: Some("fuchsia-pkg://fuchsia.com/fake_component#meta/fake_component.cm".to_string()),
4696                    startup: Some(fdecl::StartupMode::Lazy),
4697                    on_terminate: None,
4698                    environment: None,
4699                    ..Default::default()
4700                }
4701                ]);
4702                decl
4703            },
4704            result = Err(ErrorList::new(vec![
4705                Error::InvalidField(DeclField { decl: DeclType::OfferEventStream, field: "source".to_string() }),
4706            ])),
4707        },
4708
4709        test_validate_event_stream_offer_missing_source => {
4710            input = {
4711                let mut decl = new_component_decl();
4712                decl.offers = Some(vec![
4713                    fdecl::Offer::EventStream(fdecl::OfferEventStream {
4714                        source_name: Some("stopped".to_string()),
4715                        source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef{})),
4716                        target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})),
4717                        target_name: Some("stopped".to_string()),
4718                        ..Default::default()
4719                    }),
4720                    fdecl::Offer::EventStream(fdecl::OfferEventStream {
4721                        source_name: Some("started".to_string()),
4722                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
4723                        target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})),
4724                        target_name: Some("started".to_string()),
4725                        ..Default::default()
4726                    }),
4727                    fdecl::Offer::EventStream(fdecl::OfferEventStream {
4728                        source_name: Some("capability_requested".to_string()),
4729                        target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})),
4730                        target_name: Some("capability_requested".to_string()),
4731                        ..Default::default()
4732                    }),
4733                ]);
4734                decl.children = Some(vec![fdecl::Child{
4735                    name: Some("test".to_string()),
4736                    url: Some("fuchsia-pkg://fuchsia.com/logger#meta/logger.cm".to_string()),
4737                    startup: Some(fdecl::StartupMode::Lazy),
4738                    on_terminate: None,
4739                    environment: None,
4740                    ..Default::default()
4741                },
4742                fdecl::Child{
4743                    name: Some("test2".to_string()),
4744                    url: Some("fuchsia-pkg://fuchsia.com/fake_component#meta/fake_component.cm".to_string()),
4745                    startup: Some(fdecl::StartupMode::Lazy),
4746                    on_terminate: None,
4747                    environment: None,
4748                    ..Default::default()
4749                }
4750                ]);
4751                decl
4752            },
4753            result = Err(ErrorList::new(vec![
4754                Error::MissingField(DeclField { decl: DeclType::OfferEventStream, field: "source".to_string() }),
4755            ])),
4756        },
4757        test_validate_event_stream_must_have_target_path => {
4758            input = {
4759                let mut decl = new_component_decl();
4760                decl.uses = Some(vec![
4761                    fdecl::Use::EventStream(fdecl::UseEventStream {
4762                        source_name: Some("bar".to_string()),
4763                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
4764                        ..Default::default()
4765                    }),
4766                ]);
4767                decl
4768            },
4769            result = Err(ErrorList::new(vec![
4770                Error::MissingField(DeclField { decl: DeclType::UseEventStream, field: "target_path".to_string() })
4771            ])),
4772        },
4773        test_validate_event_stream_must_have_source_names => {
4774            input = {
4775                let mut decl = new_component_decl();
4776                decl.uses = Some(vec![
4777                    fdecl::Use::EventStream(fdecl::UseEventStream {
4778                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
4779                        target_path: Some("/svc/something".to_string()),
4780                        ..Default::default()
4781                    }),
4782                ]);
4783                decl
4784            },
4785            result = Err(ErrorList::new(vec![
4786                Error::MissingField(DeclField { decl: DeclType::UseEventStream, field: "source_name".to_string() })
4787            ])),
4788        },
4789        test_validate_event_stream_scope_must_be_child_or_collection => {
4790            input = {
4791                let mut decl = new_component_decl();
4792                decl.uses = Some(vec![
4793                    fdecl::Use::EventStream(fdecl::UseEventStream {
4794                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
4795                        target_path: Some("/svc/something".to_string()),
4796                        source_name: Some("some_source".to_string()),
4797                        scope: Some(vec![fdecl::Ref::Framework(fdecl::FrameworkRef{})]),
4798                        ..Default::default()
4799                    }),
4800                ]);
4801                decl
4802            },
4803            result = Err(ErrorList::new(vec![
4804                Error::InvalidField(DeclField { decl: DeclType::UseEventStream, field: "scope".to_string() })
4805            ])),
4806        },
4807        test_validate_event_stream_source_must_be_parent_or_child => {
4808            input = {
4809                let mut decl = new_component_decl();
4810                decl.uses = Some(vec![
4811                    fdecl::Use::EventStream(fdecl::UseEventStream {
4812                        source: Some(fdecl::Ref::Debug(fdecl::DebugRef{})),
4813                        target_path: Some("/svc/something".to_string()),
4814                        source_name: Some("some_source".to_string()),
4815                        scope: Some(vec![]),
4816                        ..Default::default()
4817                    }),
4818                    fdecl::Use::EventStream(fdecl::UseEventStream {
4819                        source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef{})),
4820                        target_path: Some("/svc/something_else".to_string()),
4821                        source_name: Some("some_source".to_string()),
4822                        scope: Some(vec![]),
4823                        ..Default::default()
4824                    }),
4825                    fdecl::Use::EventStream(fdecl::UseEventStream {
4826                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
4827                        target_path: Some("/svc/yet_something_else".to_string()),
4828                        source_name: Some("some_source".to_string()),
4829                        scope: Some(vec![]),
4830                        ..Default::default()
4831                    }),
4832                ]);
4833                decl
4834            },
4835            result = Err(ErrorList::new(vec![
4836                Error::InvalidField(DeclField { decl: DeclType::UseEventStream, field: "source".to_string() }),
4837                Error::InvalidField(DeclField { decl: DeclType::UseEventStream, field: "source".to_string() }),
4838                Error::InvalidField(DeclField { decl: DeclType::UseEventStream, field: "source".to_string() })
4839            ])),
4840        },
4841        test_validate_no_runner => {
4842            input = {
4843                let mut decl = new_component_decl();
4844                decl.program = Some(fdecl::Program {
4845                    runner: None,
4846                    info: Some(fdata::Dictionary {
4847                        entries: None,
4848                        ..Default::default()
4849                    }),
4850                    ..Default::default()
4851                });
4852                decl
4853            },
4854            result = Err(ErrorList::new(vec![
4855                Error::MissingRunner,
4856            ])),
4857        },
4858        test_validate_uses_runner => {
4859            input = {
4860                let mut decl = new_component_decl();
4861                decl.program = Some(fdecl::Program {
4862                    runner: None,
4863                    info: Some(fdata::Dictionary {
4864                        entries: None,
4865                        ..Default::default()
4866                    }),
4867                    ..Default::default()
4868                });
4869                decl.uses = Some(vec![
4870                    fdecl::Use::Runner(fdecl::UseRunner {
4871                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4872                        source_name: Some("runner".to_string()),
4873                        ..Default::default()
4874                    }),
4875                ]);
4876                decl
4877            },
4878            result = Ok(()),
4879        },
4880        test_validate_program_and_uses_runner_match => {
4881            input = {
4882                let mut decl = new_component_decl();
4883                decl.program = Some(fdecl::Program {
4884                    runner: Some("runner".to_string()),
4885                    info: Some(fdata::Dictionary {
4886                        entries: None,
4887                        ..Default::default()
4888                    }),
4889                    ..Default::default()
4890                });
4891                decl.uses = Some(vec![
4892                    fdecl::Use::Runner(fdecl::UseRunner {
4893                        source: Some(fdecl::Ref::Environment(fdecl::EnvironmentRef {})),
4894                        source_name: Some("runner".to_string()),
4895                        ..Default::default()
4896                    }),
4897                ]);
4898                decl
4899            },
4900            result = Ok(()),
4901        },
4902        test_validate_runner_names_conflict => {
4903            input = {
4904                let mut decl = new_component_decl();
4905                decl.program = Some(fdecl::Program {
4906                    runner: Some("runner".to_string()),
4907                    info: Some(fdata::Dictionary {
4908                        entries: None,
4909                        ..Default::default()
4910                    }),
4911                    ..Default::default()
4912                });
4913                decl.uses = Some(vec![
4914                    fdecl::Use::Runner(fdecl::UseRunner {
4915                        source: Some(fdecl::Ref::Environment(fdecl::EnvironmentRef {})),
4916                        source_name: Some("other.runner".to_string()),
4917                        ..Default::default()
4918                    }),
4919                ]);
4920                decl
4921            },
4922            result = Err(ErrorList::new(vec![
4923                Error::ConflictingRunners,
4924            ])),
4925        },
4926        test_validate_uses_runner_not_environement => {
4927            input = {
4928                let mut decl = new_component_decl();
4929                decl.program = Some(fdecl::Program {
4930                    runner: Some("runner".to_string()),
4931                    info: Some(fdata::Dictionary {
4932                        entries: None,
4933                        ..Default::default()
4934                    }),
4935                    ..Default::default()
4936                });
4937                decl.uses = Some(vec![
4938                    fdecl::Use::Runner(fdecl::UseRunner {
4939                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4940                        source_name: Some("runner".to_string()),
4941                        ..Default::default()
4942                    }),
4943                ]);
4944                decl
4945            },
4946            result = Err(ErrorList::new(vec![
4947                Error::ConflictingRunners,
4948            ])),
4949        },
4950        test_validate_uses_long_identifiers => {
4951            input = {
4952                let mut decl = new_component_decl();
4953                decl.program = Some(fdecl::Program {
4954                    runner: Some("elf".to_string()),
4955                    info: Some(fdata::Dictionary {
4956                        entries: None,
4957                        ..Default::default()
4958                    }),
4959                    ..Default::default()
4960                });
4961                decl.uses = Some(vec![
4962                    fdecl::Use::Service(fdecl::UseService {
4963                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4964                        source_name: Some(format!("{}", "a".repeat(256))),
4965                        target_path: Some("/a".repeat(2048)),
4966                        dependency_type: Some(fdecl::DependencyType::Strong),
4967                        ..Default::default()
4968                    }),
4969                    fdecl::Use::Protocol(fdecl::UseProtocol {
4970                        dependency_type: Some(fdecl::DependencyType::Strong),
4971                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4972                        source_name: Some(format!("{}", "a".repeat(256))),
4973                        target_path: Some("/b".repeat(2048)),
4974                        ..Default::default()
4975                    }),
4976                    fdecl::Use::Directory(fdecl::UseDirectory {
4977                        dependency_type: Some(fdecl::DependencyType::Strong),
4978                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4979                        source_name: Some(format!("{}", "a".repeat(256))),
4980                        target_path: Some("/c".repeat(2048)),
4981                        rights: Some(fio::Operations::CONNECT),
4982                        subdir: None,
4983                        ..Default::default()
4984                    }),
4985                    fdecl::Use::Storage(fdecl::UseStorage {
4986                        source_name: Some("cache".to_string()),
4987                        target_path: Some("/d".repeat(2048)),
4988                        ..Default::default()
4989                    }),
4990                ]);
4991                decl
4992            },
4993            result = Err(ErrorList::new(vec![
4994                Error::field_too_long(DeclType::UseService, "source_name"),
4995                Error::field_too_long(DeclType::UseService, "target_path"),
4996                Error::field_too_long(DeclType::UseProtocol, "source_name"),
4997                Error::field_too_long(DeclType::UseProtocol, "target_path"),
4998                Error::field_too_long(DeclType::UseDirectory, "source_name"),
4999                Error::field_too_long(DeclType::UseDirectory, "target_path"),
5000                Error::field_too_long(DeclType::UseStorage, "target_path"),
5001            ])),
5002        },
5003        test_validate_conflicting_paths => {
5004            input = {
5005                let mut decl = new_component_decl();
5006                decl.uses = Some(vec![
5007                    fdecl::Use::Service(fdecl::UseService {
5008                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5009                        source_name: Some("foo".to_string()),
5010                        target_path: Some("/bar".to_string()),
5011                        dependency_type: Some(fdecl::DependencyType::Strong),
5012                        ..Default::default()
5013                    }),
5014                    fdecl::Use::Protocol(fdecl::UseProtocol {
5015                        dependency_type: Some(fdecl::DependencyType::Strong),
5016                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5017                        source_name: Some("space".to_string()),
5018                        target_path: Some("/bar".to_string()),
5019                        ..Default::default()
5020                    }),
5021                    fdecl::Use::Directory(fdecl::UseDirectory {
5022                        dependency_type: Some(fdecl::DependencyType::Strong),
5023                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5024                        source_name: Some("crow".to_string()),
5025                        target_path: Some("/bar".to_string()),
5026                        rights: Some(fio::Operations::CONNECT),
5027                        subdir: None,
5028                        ..Default::default()
5029                    }),
5030                ]);
5031                decl
5032            },
5033            result = Err(ErrorList::new(vec![
5034                Error::duplicate_field(DeclType::UseProtocol, "target_path", "/bar"),
5035                Error::duplicate_field(DeclType::UseDirectory, "target_path", "/bar"),
5036            ])),
5037        },
5038        // exposes
5039        test_validate_exposes_empty => {
5040            input = {
5041                let mut decl = new_component_decl();
5042                decl.exposes = Some(vec![
5043                    fdecl::Expose::Service(fdecl::ExposeService {
5044                        source: None,
5045                        source_name: None,
5046                        target_name: None,
5047                        target: None,
5048                        ..Default::default()
5049                    }),
5050                    fdecl::Expose::Protocol(fdecl::ExposeProtocol {
5051                        source: None,
5052                        source_name: None,
5053                        target_name: None,
5054                        target: None,
5055                        ..Default::default()
5056                    }),
5057                    fdecl::Expose::Directory(fdecl::ExposeDirectory {
5058                        source: None,
5059                        source_name: None,
5060                        target_name: None,
5061                        target: None,
5062                        rights: None,
5063                        subdir: None,
5064                        ..Default::default()
5065                    }),
5066                    fdecl::Expose::Runner(fdecl::ExposeRunner {
5067                        source: None,
5068                        source_name: None,
5069                        target: None,
5070                        target_name: None,
5071                        ..Default::default()
5072                    }),
5073                    fdecl::Expose::Resolver(fdecl::ExposeResolver {
5074                        source: None,
5075                        source_name: None,
5076                        target: None,
5077                        target_name: None,
5078                        ..Default::default()
5079                    }),
5080                    fdecl::Expose::Dictionary(fdecl::ExposeDictionary {
5081                        ..Default::default()
5082                    }),
5083                ]);
5084                decl
5085            },
5086            result = Err(ErrorList::new(vec![
5087                Error::missing_field(DeclType::ExposeService, "source"),
5088                Error::missing_field(DeclType::ExposeService, "target"),
5089                Error::missing_field(DeclType::ExposeService, "source_name"),
5090                Error::missing_field(DeclType::ExposeService, "target_name"),
5091                Error::missing_field(DeclType::ExposeProtocol, "source"),
5092                Error::missing_field(DeclType::ExposeProtocol, "target"),
5093                Error::missing_field(DeclType::ExposeProtocol, "source_name"),
5094                Error::missing_field(DeclType::ExposeProtocol, "target_name"),
5095                Error::missing_field(DeclType::ExposeDirectory, "source"),
5096                Error::missing_field(DeclType::ExposeDirectory, "target"),
5097                Error::missing_field(DeclType::ExposeDirectory, "source_name"),
5098                Error::missing_field(DeclType::ExposeDirectory, "target_name"),
5099                Error::missing_field(DeclType::ExposeRunner, "source"),
5100                Error::missing_field(DeclType::ExposeRunner, "target"),
5101                Error::missing_field(DeclType::ExposeRunner, "source_name"),
5102                Error::missing_field(DeclType::ExposeRunner, "target_name"),
5103                Error::missing_field(DeclType::ExposeResolver, "source"),
5104                Error::missing_field(DeclType::ExposeResolver, "target"),
5105                Error::missing_field(DeclType::ExposeResolver, "source_name"),
5106                Error::missing_field(DeclType::ExposeResolver, "target_name"),
5107                Error::missing_field(DeclType::ExposeDictionary, "source"),
5108                Error::missing_field(DeclType::ExposeDictionary, "target"),
5109                Error::missing_field(DeclType::ExposeDictionary, "source_name"),
5110                Error::missing_field(DeclType::ExposeDictionary, "target_name"),
5111            ])),
5112        },
5113        test_validate_exposes_extraneous => {
5114            input = {
5115                let mut decl = new_component_decl();
5116                decl.exposes = Some(vec![
5117                    fdecl::Expose::Service(fdecl::ExposeService {
5118                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5119                            name: "logger".to_string(),
5120                            collection: Some("modular".to_string()),
5121                        })),
5122                        source_name: Some("logger".to_string()),
5123                        target_name: Some("logger".to_string()),
5124                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5125                        ..Default::default()
5126                    }),
5127                    fdecl::Expose::Protocol(fdecl::ExposeProtocol {
5128                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5129                            name: "logger".to_string(),
5130                            collection: Some("modular".to_string()),
5131                        })),
5132                        source_name: Some("legacy_logger".to_string()),
5133                        target_name: Some("legacy_logger".to_string()),
5134                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5135                        ..Default::default()
5136                    }),
5137                    fdecl::Expose::Directory(fdecl::ExposeDirectory {
5138                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5139                            name: "netstack".to_string(),
5140                            collection: Some("modular".to_string()),
5141                        })),
5142                        source_name: Some("data".to_string()),
5143                        target_name: Some("data".to_string()),
5144                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5145                        rights: Some(fio::Operations::CONNECT),
5146                        subdir: None,
5147                        ..Default::default()
5148                    }),
5149                    fdecl::Expose::Runner(fdecl::ExposeRunner {
5150                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5151                            name: "netstack".to_string(),
5152                            collection: Some("modular".to_string()),
5153                        })),
5154                        source_name: Some("elf".to_string()),
5155                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5156                        target_name: Some("elf".to_string()),
5157                        ..Default::default()
5158                    }),
5159                    fdecl::Expose::Resolver(fdecl::ExposeResolver {
5160                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5161                            name: "netstack".to_string(),
5162                            collection: Some("modular".to_string()),
5163                        })),
5164                        source_name: Some("pkg".to_string()),
5165                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5166                        target_name: Some("pkg".to_string()),
5167                        ..Default::default()
5168                    }),
5169                    fdecl::Expose::Dictionary(fdecl::ExposeDictionary {
5170                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5171                            name: "netstack".to_string(),
5172                            collection: Some("modular".to_string()),
5173                        })),
5174                        source_name: Some("dict".to_string()),
5175                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5176                        target_name: Some("dict".to_string()),
5177                        ..Default::default()
5178                    }),
5179                ]);
5180                decl
5181            },
5182            result = Err(ErrorList::new(vec![
5183                Error::extraneous_field(DeclType::ExposeService, "source.child.collection"),
5184                Error::extraneous_field(DeclType::ExposeProtocol, "source.child.collection"),
5185                Error::extraneous_field(DeclType::ExposeDirectory, "source.child.collection"),
5186                Error::extraneous_field(DeclType::ExposeRunner, "source.child.collection"),
5187                Error::extraneous_field(DeclType::ExposeResolver, "source.child.collection"),
5188                Error::extraneous_field(DeclType::ExposeDictionary, "source.child.collection"),
5189            ])),
5190        },
5191        test_validate_exposes_invalid_identifiers => {
5192            input = {
5193                let mut decl = new_component_decl();
5194                decl.exposes = Some(vec![
5195                    fdecl::Expose::Service(fdecl::ExposeService {
5196                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5197                            name: "^bad".to_string(),
5198                            collection: None,
5199                        })),
5200                        source_name: Some("foo/".to_string()),
5201                        target_name: Some("/".to_string()),
5202                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5203                        ..Default::default()
5204                    }),
5205                    fdecl::Expose::Protocol(fdecl::ExposeProtocol {
5206                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5207                            name: "^bad".to_string(),
5208                            collection: None,
5209                        })),
5210                        source_name: Some("foo/".to_string()),
5211                        target_name: Some("/".to_string()),
5212                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5213                        ..Default::default()
5214                    }),
5215                    fdecl::Expose::Directory(fdecl::ExposeDirectory {
5216                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5217                            name: "^bad".to_string(),
5218                            collection: None,
5219                        })),
5220                        source_name: Some("foo/".to_string()),
5221                        target_name: Some("/".to_string()),
5222                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5223                        rights: Some(fio::Operations::CONNECT),
5224                        subdir: Some("/foo".to_string()),
5225                        ..Default::default()
5226                    }),
5227                    fdecl::Expose::Runner(fdecl::ExposeRunner {
5228                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5229                            name: "^bad".to_string(),
5230                            collection: None,
5231                        })),
5232                        source_name: Some("/path".to_string()),
5233                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5234                        target_name: Some("elf!".to_string()),
5235                        ..Default::default()
5236                    }),
5237                    fdecl::Expose::Resolver(fdecl::ExposeResolver {
5238                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5239                            name: "^bad".to_string(),
5240                            collection: None,
5241                        })),
5242                        source_name: Some("/path".to_string()),
5243                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5244                        target_name: Some("pkg!".to_string()),
5245                        ..Default::default()
5246                    }),
5247                    fdecl::Expose::Dictionary(fdecl::ExposeDictionary {
5248                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5249                            name: "^bad".to_string(),
5250                            collection: None,
5251                        })),
5252                        source_name: Some("/path".to_string()),
5253                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5254                        target_name: Some("pkg!".to_string()),
5255                        ..Default::default()
5256                    }),
5257                ]);
5258                decl
5259            },
5260            result = Err(ErrorList::new(vec![
5261                Error::invalid_field(DeclType::ExposeService, "source.child.name"),
5262                Error::invalid_field(DeclType::ExposeService, "source_name"),
5263                Error::invalid_field(DeclType::ExposeService, "target_name"),
5264                Error::invalid_field(DeclType::ExposeProtocol, "source.child.name"),
5265                Error::invalid_field(DeclType::ExposeProtocol, "source_name"),
5266                Error::invalid_field(DeclType::ExposeProtocol, "target_name"),
5267                Error::invalid_field(DeclType::ExposeDirectory, "source.child.name"),
5268                Error::invalid_field(DeclType::ExposeDirectory, "source_name"),
5269                Error::invalid_field(DeclType::ExposeDirectory, "target_name"),
5270                Error::invalid_field(DeclType::ExposeDirectory, "subdir"),
5271                Error::invalid_field(DeclType::ExposeRunner, "source.child.name"),
5272                Error::invalid_field(DeclType::ExposeRunner, "source_name"),
5273                Error::invalid_field(DeclType::ExposeRunner, "target_name"),
5274                Error::invalid_field(DeclType::ExposeResolver, "source.child.name"),
5275                Error::invalid_field(DeclType::ExposeResolver, "source_name"),
5276                Error::invalid_field(DeclType::ExposeResolver, "target_name"),
5277                Error::invalid_field(DeclType::ExposeDictionary, "source.child.name"),
5278                Error::invalid_field(DeclType::ExposeDictionary, "source_name"),
5279                Error::invalid_field(DeclType::ExposeDictionary, "target_name"),
5280            ])),
5281        },
5282        test_validate_exposes_invalid_source_target => {
5283            input = {
5284                let mut decl = new_component_decl();
5285                decl.children = Some(vec![fdecl::Child{
5286                    name: Some("logger".to_string()),
5287                    url: Some("fuchsia-pkg://fuchsia.com/logger#meta/logger.cm".to_string()),
5288                    startup: Some(fdecl::StartupMode::Lazy),
5289                    on_terminate: None,
5290                    environment: None,
5291                    ..Default::default()
5292                }]);
5293                decl.exposes = Some(vec![
5294                    fdecl::Expose::Service(fdecl::ExposeService {
5295                        source: None,
5296                        source_name: Some("a".to_string()),
5297                        target_name: Some("b".to_string()),
5298                        target: None,
5299                        ..Default::default()
5300                    }),
5301                    fdecl::Expose::Protocol(fdecl::ExposeProtocol {
5302                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5303                        source_name: Some("c".to_string()),
5304                        target_name: Some("d".to_string()),
5305                        target: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
5306                        ..Default::default()
5307                    }),
5308                    fdecl::Expose::Directory(fdecl::ExposeDirectory {
5309                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {name: "z".to_string()})),
5310                        source_name: Some("e".to_string()),
5311                        target_name: Some("f".to_string()),
5312                        target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {name: "z".to_string()})),
5313                        rights: Some(fio::Operations::CONNECT),
5314                        subdir: None,
5315                        ..Default::default()
5316                    }),
5317                    fdecl::Expose::Directory(fdecl::ExposeDirectory {
5318                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5319                        source_name: Some("g".to_string()),
5320                        target_name: Some("h".to_string()),
5321                        target: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
5322                        rights: Some(fio::Operations::CONNECT),
5323                        subdir: None,
5324                        ..Default::default()
5325                    }),
5326                    fdecl::Expose::Runner(fdecl::ExposeRunner {
5327                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5328                        source_name: Some("i".to_string()),
5329                        target: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
5330                        target_name: Some("j".to_string()),
5331                        ..Default::default()
5332                    }),
5333                    fdecl::Expose::Resolver(fdecl::ExposeResolver {
5334                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5335                        source_name: Some("k".to_string()),
5336                        target: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
5337                        target_name: Some("l".to_string()),
5338                        ..Default::default()
5339                    }),
5340                    fdecl::Expose::Dictionary(fdecl::ExposeDictionary {
5341                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5342                            name: "logger".to_string(),
5343                            collection: None,
5344                        })),
5345                        source_name: Some("m".to_string()),
5346                        target_name: Some("n".to_string()),
5347                        target: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
5348                        ..Default::default()
5349                    }),
5350                ]);
5351                decl
5352            },
5353            result = Err(ErrorList::new(vec![
5354                Error::missing_field(DeclType::ExposeService, "source"),
5355                Error::missing_field(DeclType::ExposeService, "target"),
5356                Error::invalid_field(DeclType::ExposeProtocol, "source"),
5357                Error::invalid_field(DeclType::ExposeProtocol, "target"),
5358                Error::invalid_field(DeclType::ExposeDirectory, "source"),
5359                Error::invalid_field(DeclType::ExposeDirectory, "target"),
5360                Error::invalid_field(DeclType::ExposeDirectory, "source"),
5361                Error::invalid_field(DeclType::ExposeDirectory, "target"),
5362                Error::invalid_field(DeclType::ExposeRunner, "source"),
5363                Error::invalid_field(DeclType::ExposeRunner, "target"),
5364                Error::invalid_field(DeclType::ExposeResolver, "source"),
5365                Error::invalid_field(DeclType::ExposeResolver, "target"),
5366                Error::invalid_field(DeclType::ExposeDictionary, "target"),
5367            ])),
5368        },
5369        test_validate_exposes_invalid_source_collection => {
5370            input = {
5371                let mut decl = new_component_decl();
5372                decl.collections = Some(vec![fdecl::Collection{
5373                    name: Some("col".to_string()),
5374                    durability: Some(fdecl::Durability::Transient),
5375                    allowed_offers: None,
5376                    allow_long_names: None,
5377                    ..Default::default()
5378                }]);
5379                decl.exposes = Some(vec![
5380                    fdecl::Expose::Protocol(fdecl::ExposeProtocol {
5381                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef { name: "col".to_string() })),
5382                        source_name: Some("a".to_string()),
5383                        target_name: Some("a".to_string()),
5384                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5385                        ..Default::default()
5386                    }),
5387                    fdecl::Expose::Directory(fdecl::ExposeDirectory {
5388                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {name: "col".to_string()})),
5389                        source_name: Some("b".to_string()),
5390                        target_name: Some("b".to_string()),
5391                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5392                        rights: Some(fio::Operations::CONNECT),
5393                        subdir: None,
5394                        ..Default::default()
5395                    }),
5396                    fdecl::Expose::Runner(fdecl::ExposeRunner {
5397                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {name: "col".to_string()})),
5398                        source_name: Some("c".to_string()),
5399                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5400                        target_name: Some("c".to_string()),
5401                        ..Default::default()
5402                    }),
5403                    fdecl::Expose::Resolver(fdecl::ExposeResolver {
5404                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {name: "col".to_string()})),
5405                        source_name: Some("d".to_string()),
5406                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5407                        target_name: Some("d".to_string()),
5408                        ..Default::default()
5409                    }),
5410                    fdecl::Expose::Dictionary(fdecl::ExposeDictionary {
5411                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {name: "col".to_string()})),
5412                        source_name: Some("e".to_string()),
5413                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5414                        target_name: Some("e".to_string()),
5415                        ..Default::default()
5416                    }),
5417                ]);
5418                decl
5419            },
5420            result = Err(ErrorList::new(vec![
5421                Error::invalid_field(DeclType::ExposeProtocol, "source"),
5422                Error::invalid_field(DeclType::ExposeDirectory, "source"),
5423                Error::invalid_field(DeclType::ExposeRunner, "source"),
5424                Error::invalid_field(DeclType::ExposeResolver, "source"),
5425                Error::invalid_field(DeclType::ExposeDictionary, "source"),
5426            ])),
5427        },
5428        test_validate_exposes_sources_collection => {
5429            input = {
5430                let mut decl = new_component_decl();
5431                decl.collections = Some(vec![
5432                    fdecl::Collection {
5433                        name: Some("col".to_string()),
5434                        durability: Some(fdecl::Durability::Transient),
5435                        allowed_offers: Some(fdecl::AllowedOffers::StaticOnly),
5436                        allow_long_names: None,
5437                        ..Default::default()
5438                    }
5439                ]);
5440                decl.exposes = Some(vec![
5441                    fdecl::Expose::Service(fdecl::ExposeService {
5442                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef { name: "col".to_string() })),
5443                        source_name: Some("a".to_string()),
5444                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5445                        target_name: Some("a".to_string()),
5446                        ..Default::default()
5447                    })
5448                ]);
5449                decl
5450            },
5451            result = Ok(()),
5452        },
5453        test_validate_exposes_long_identifiers => {
5454            input = {
5455                let mut decl = new_component_decl();
5456                decl.exposes = Some(vec![
5457                    fdecl::Expose::Service(fdecl::ExposeService {
5458                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5459                            name: "b".repeat(256),
5460                            collection: None,
5461                        })),
5462                        source_name: Some(format!("{}", "a".repeat(1025))),
5463                        target_name: Some(format!("{}", "b".repeat(1025))),
5464                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5465                        ..Default::default()
5466                    }),
5467                    fdecl::Expose::Protocol(fdecl::ExposeProtocol {
5468                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5469                            name: "b".repeat(256),
5470                            collection: None,
5471                        })),
5472                        source_name: Some(format!("{}", "a".repeat(256))),
5473                        target_name: Some(format!("{}", "b".repeat(256))),
5474                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5475                        ..Default::default()
5476                    }),
5477                    fdecl::Expose::Directory(fdecl::ExposeDirectory {
5478                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5479                            name: "b".repeat(256),
5480                            collection: None,
5481                        })),
5482                        source_name: Some(format!("{}", "a".repeat(256))),
5483                        target_name: Some(format!("{}", "b".repeat(256))),
5484                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5485                        rights: Some(fio::Operations::CONNECT),
5486                        subdir: None,
5487                        ..Default::default()
5488                    }),
5489                    fdecl::Expose::Runner(fdecl::ExposeRunner {
5490                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5491                            name: "b".repeat(256),
5492                            collection: None,
5493                        })),
5494                        source_name: Some("a".repeat(256)),
5495                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5496                        target_name: Some("b".repeat(256)),
5497                        ..Default::default()
5498                    }),
5499                    fdecl::Expose::Resolver(fdecl::ExposeResolver {
5500                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5501                            name: "b".repeat(256),
5502                            collection: None,
5503                        })),
5504                        source_name: Some("a".repeat(256)),
5505                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5506                        target_name: Some("b".repeat(256)),
5507                        ..Default::default()
5508                    }),
5509                    fdecl::Expose::Dictionary(fdecl::ExposeDictionary {
5510                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5511                            name: "b".repeat(256),
5512                            collection: None,
5513                        })),
5514                        source_name: Some("a".repeat(256)),
5515                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5516                        target_name: Some("b".repeat(256)),
5517                        ..Default::default()
5518                    }),
5519                ]);
5520                decl
5521            },
5522            result = Err(ErrorList::new(vec![
5523                Error::field_too_long(DeclType::ExposeService, "source.child.name"),
5524                Error::field_too_long(DeclType::ExposeService, "source_name"),
5525                Error::field_too_long(DeclType::ExposeService, "target_name"),
5526                Error::field_too_long(DeclType::ExposeProtocol, "source.child.name"),
5527                Error::field_too_long(DeclType::ExposeProtocol, "source_name"),
5528                Error::field_too_long(DeclType::ExposeProtocol, "target_name"),
5529                Error::field_too_long(DeclType::ExposeDirectory, "source.child.name"),
5530                Error::field_too_long(DeclType::ExposeDirectory, "source_name"),
5531                Error::field_too_long(DeclType::ExposeDirectory, "target_name"),
5532                Error::field_too_long(DeclType::ExposeRunner, "source.child.name"),
5533                Error::field_too_long(DeclType::ExposeRunner, "source_name"),
5534                Error::field_too_long(DeclType::ExposeRunner, "target_name"),
5535                Error::field_too_long(DeclType::ExposeResolver, "source.child.name"),
5536                Error::field_too_long(DeclType::ExposeResolver, "source_name"),
5537                Error::field_too_long(DeclType::ExposeResolver, "target_name"),
5538                Error::field_too_long(DeclType::ExposeDictionary, "source.child.name"),
5539                Error::field_too_long(DeclType::ExposeDictionary, "source_name"),
5540                Error::field_too_long(DeclType::ExposeDictionary, "target_name"),
5541            ])),
5542        },
5543        test_validate_exposes_invalid_child => {
5544            input = {
5545                let mut decl = new_component_decl();
5546                decl.exposes = Some(vec![
5547                    fdecl::Expose::Service(fdecl::ExposeService {
5548                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5549                            name: "netstack".to_string(),
5550                            collection: None,
5551                        })),
5552                        source_name: Some("fuchsia.logger.Log".to_string()),
5553                        target_name: Some("fuchsia.logger.Log".to_string()),
5554                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5555                        ..Default::default()
5556                    }),
5557                    fdecl::Expose::Protocol(fdecl::ExposeProtocol {
5558                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5559                            name: "netstack".to_string(),
5560                            collection: None,
5561                        })),
5562                        source_name: Some("fuchsia.logger.LegacyLog".to_string()),
5563                        target_name: Some("fuchsia.logger.LegacyLog".to_string()),
5564                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5565                        ..Default::default()
5566                    }),
5567                    fdecl::Expose::Directory(fdecl::ExposeDirectory {
5568                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5569                            name: "netstack".to_string(),
5570                            collection: None,
5571                        })),
5572                        source_name: Some("data".to_string()),
5573                        target_name: Some("data".to_string()),
5574                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5575                        rights: Some(fio::Operations::CONNECT),
5576                        subdir: None,
5577                        ..Default::default()
5578                    }),
5579                    fdecl::Expose::Runner(fdecl::ExposeRunner {
5580                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5581                            name: "netstack".to_string(),
5582                            collection: None,
5583                        })),
5584                        source_name: Some("elf".to_string()),
5585                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5586                        target_name: Some("elf".to_string()),
5587                        ..Default::default()
5588                    }),
5589                    fdecl::Expose::Resolver(fdecl::ExposeResolver {
5590                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5591                            name: "netstack".to_string(),
5592                            collection: None,
5593                        })),
5594                        source_name: Some("pkg".to_string()),
5595                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5596                        target_name: Some("pkg".to_string()),
5597                        ..Default::default()
5598                    }),
5599                    fdecl::Expose::Dictionary(fdecl::ExposeDictionary {
5600                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5601                            name: "netstack".to_string(),
5602                            collection: None,
5603                        })),
5604                        source_name: Some("dict".to_string()),
5605                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5606                        target_name: Some("dict".to_string()),
5607                        ..Default::default()
5608                    }),
5609                ]);
5610                decl
5611            },
5612            result = Err(ErrorList::new(vec![
5613                Error::invalid_child(DeclType::ExposeService, "source", "netstack"),
5614                Error::invalid_child(DeclType::ExposeProtocol, "source", "netstack"),
5615                Error::invalid_child(DeclType::ExposeDirectory, "source", "netstack"),
5616                Error::invalid_child(DeclType::ExposeRunner, "source", "netstack"),
5617                Error::invalid_child(DeclType::ExposeResolver, "source", "netstack"),
5618                Error::invalid_child(DeclType::ExposeDictionary, "source", "netstack"),
5619            ])),
5620        },
5621        test_validate_exposes_invalid_source_capability => {
5622            input = {
5623                fdecl::Component {
5624                    exposes: Some(vec![
5625                        fdecl::Expose::Protocol(fdecl::ExposeProtocol {
5626                            source: Some(fdecl::Ref::Capability(fdecl::CapabilityRef {
5627                                name: "this-storage-doesnt-exist".to_string(),
5628                            })),
5629                            source_name: Some("fuchsia.sys2.StorageAdmin".to_string()),
5630                            target_name: Some("fuchsia.sys2.StorageAdmin".to_string()),
5631                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5632                            ..Default::default()
5633                        }),
5634                    ]),
5635                    ..new_component_decl()
5636                }
5637            },
5638            result = Err(ErrorList::new(vec![
5639                Error::invalid_capability(DeclType::ExposeProtocol, "source", "this-storage-doesnt-exist"),
5640            ])),
5641        },
5642        test_validate_exposes_duplicate_target => {
5643            input = {
5644                let mut decl = new_component_decl();
5645                decl.exposes = Some(vec![
5646                    fdecl::Expose::Service(fdecl::ExposeService {
5647                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
5648                            name: "coll".into(),
5649                        })),
5650                        source_name: Some("netstack".to_string()),
5651                        target_name: Some("fuchsia.net.Stack".to_string()),
5652                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5653                        ..Default::default()
5654                    }),
5655                    fdecl::Expose::Service(fdecl::ExposeService {
5656                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
5657                            name: "coll2".into(),
5658                        })),
5659                        source_name: Some("netstack2".to_string()),
5660                        target_name: Some("fuchsia.net.Stack".to_string()),
5661                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5662                        ..Default::default()
5663                    }),
5664                    fdecl::Expose::Protocol(fdecl::ExposeProtocol {
5665                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
5666                        source_name: Some("fonts".to_string()),
5667                        target_name: Some("fuchsia.fonts.Provider".to_string()),
5668                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5669                        ..Default::default()
5670                    }),
5671                    fdecl::Expose::Protocol(fdecl::ExposeProtocol {
5672                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
5673                        source_name: Some("fonts2".to_string()),
5674                        target_name: Some("fuchsia.fonts.Provider".to_string()),
5675                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5676                        ..Default::default()
5677                    }),
5678                    fdecl::Expose::Directory(fdecl::ExposeDirectory {
5679                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
5680                        source_name: Some("assets".to_string()),
5681                        target_name: Some("stuff".to_string()),
5682                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5683                        rights: None,
5684                        subdir: None,
5685                        ..Default::default()
5686                    }),
5687                    fdecl::Expose::Directory(fdecl::ExposeDirectory {
5688                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
5689                        source_name: Some("assets2".to_string()),
5690                        target_name: Some("stuff".to_string()),
5691                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5692                        rights: None,
5693                        subdir: None,
5694                        ..Default::default()
5695                    }),
5696                    fdecl::Expose::Runner(fdecl::ExposeRunner {
5697                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
5698                        source_name: Some("source_elf".to_string()),
5699                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5700                        target_name: Some("elf".to_string()),
5701                        ..Default::default()
5702                    }),
5703                    fdecl::Expose::Runner(fdecl::ExposeRunner {
5704                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
5705                        source_name: Some("source_elf".to_string()),
5706                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5707                        target_name: Some("elf".to_string()),
5708                        ..Default::default()
5709                    }),
5710                    fdecl::Expose::Resolver(fdecl::ExposeResolver {
5711                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
5712                        source_name: Some("source_pkg".to_string()),
5713                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5714                        target_name: Some("pkg".to_string()),
5715                        ..Default::default()
5716                    }),
5717                    fdecl::Expose::Resolver(fdecl::ExposeResolver {
5718                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
5719                        source_name: Some("source_pkg".to_string()),
5720                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5721                        target_name: Some("pkg".to_string()),
5722                        ..Default::default()
5723                    }),
5724                    fdecl::Expose::Dictionary(fdecl::ExposeDictionary {
5725                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
5726                        source_name: Some("source_dict".to_string()),
5727                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5728                        target_name: Some("dict".to_string()),
5729                        ..Default::default()
5730                    }),
5731                    fdecl::Expose::Dictionary(fdecl::ExposeDictionary {
5732                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
5733                        source_name: Some("source_dict".to_string()),
5734                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5735                        target_name: Some("dict".to_string()),
5736                        ..Default::default()
5737                    }),
5738                ]);
5739                decl.collections = Some(vec![
5740                    fdecl::Collection {
5741                        name: Some("coll".into()),
5742                        durability: Some(fdecl::Durability::Transient),
5743                        ..Default::default()
5744                    },
5745                    fdecl::Collection {
5746                        name: Some("coll2".into()),
5747                        durability: Some(fdecl::Durability::Transient),
5748                        ..Default::default()
5749                    },
5750                ]);
5751                decl.capabilities = Some(vec![
5752                    fdecl::Capability::Service(fdecl::Service {
5753                        name: Some("netstack".to_string()),
5754                        source_path: Some("/path".to_string()),
5755                        ..Default::default()
5756                    }),
5757                    fdecl::Capability::Service(fdecl::Service {
5758                        name: Some("netstack2".to_string()),
5759                        source_path: Some("/path".to_string()),
5760                        ..Default::default()
5761                    }),
5762                    fdecl::Capability::Protocol(fdecl::Protocol {
5763                        name: Some("fonts".to_string()),
5764                        source_path: Some("/path".to_string()),
5765                        ..Default::default()
5766                    }),
5767                    fdecl::Capability::Protocol(fdecl::Protocol {
5768                        name: Some("fonts2".to_string()),
5769                        source_path: Some("/path".to_string()),
5770                        ..Default::default()
5771                    }),
5772                    fdecl::Capability::Directory(fdecl::Directory {
5773                        name: Some("assets".to_string()),
5774                        source_path: Some("/path".to_string()),
5775                        rights: Some(fio::Operations::CONNECT),
5776                        ..Default::default()
5777                    }),
5778                    fdecl::Capability::Directory(fdecl::Directory {
5779                        name: Some("assets2".to_string()),
5780                        source_path: Some("/path".to_string()),
5781                        rights: Some(fio::Operations::CONNECT),
5782                        ..Default::default()
5783                    }),
5784                    fdecl::Capability::Runner(fdecl::Runner {
5785                        name: Some("source_elf".to_string()),
5786                        source_path: Some("/path".to_string()),
5787                        ..Default::default()
5788                    }),
5789                    fdecl::Capability::Resolver(fdecl::Resolver {
5790                        name: Some("source_pkg".to_string()),
5791                        source_path: Some("/path".to_string()),
5792                        ..Default::default()
5793                    }),
5794                    fdecl::Capability::Dictionary(fdecl::Dictionary {
5795                        name: Some("source_dict".to_string()),
5796                        ..Default::default()
5797                    }),
5798                ]);
5799                decl
5800            },
5801            result = Err(ErrorList::new(vec![
5802                // Duplicate services are allowed.
5803                Error::duplicate_field(DeclType::ExposeProtocol, "target_name",
5804                                    "fuchsia.fonts.Provider"),
5805                Error::duplicate_field(DeclType::ExposeDirectory, "target_name",
5806                                    "stuff"),
5807                Error::duplicate_field(DeclType::ExposeRunner, "target_name",
5808                                    "elf"),
5809                Error::duplicate_field(DeclType::ExposeResolver, "target_name", "pkg"),
5810                Error::duplicate_field(DeclType::ExposeDictionary, "target_name", "dict"),
5811            ])),
5812        },
5813        test_validate_exposes_invalid_capability_from_self => {
5814            input = {
5815                let mut decl = new_component_decl();
5816                decl.exposes = Some(vec![
5817                    fdecl::Expose::Service(fdecl::ExposeService {
5818                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
5819                        source_name: Some("fuchsia.some.library.SomeProtocol".to_string()),
5820                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5821                        target_name: Some("foo".to_string()),
5822                        ..Default::default()
5823                    }),
5824                    fdecl::Expose::Protocol(fdecl::ExposeProtocol {
5825                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
5826                        source_name: Some("fuchsia.some.library.SomeProtocol".to_string()),
5827                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5828                        target_name: Some("bar".to_string()),
5829                        ..Default::default()
5830                    }),
5831                    fdecl::Expose::Directory(fdecl::ExposeDirectory {
5832                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
5833                        source_name: Some("dir".to_string()),
5834                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5835                        target_name: Some("assets".to_string()),
5836                        rights: None,
5837                        subdir: None,
5838                        ..Default::default()
5839                    }),
5840                    fdecl::Expose::Runner(fdecl::ExposeRunner {
5841                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
5842                        source_name: Some("source_elf".to_string()),
5843                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5844                        target_name: Some("elf".to_string()),
5845                        ..Default::default()
5846                    }),
5847                    fdecl::Expose::Resolver(fdecl::ExposeResolver {
5848                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
5849                        source_name: Some("source_pkg".to_string()),
5850                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5851                        target_name: Some("pkg".to_string()),
5852                        ..Default::default()
5853                    }),
5854                    fdecl::Expose::Dictionary(fdecl::ExposeDictionary {
5855                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
5856                        source_name: Some("source_dict".to_string()),
5857                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5858                        target_name: Some("dict".to_string()),
5859                        ..Default::default()
5860                    }),
5861                    fdecl::Expose::Config(fdecl::ExposeConfiguration {
5862                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
5863                        source_name: Some("source_config".to_string()),
5864                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5865                        target_name: Some("config".to_string()),
5866                        ..Default::default()
5867                    }),
5868                    fdecl::Expose::Protocol(fdecl::ExposeProtocol {
5869                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
5870                        source_name: Some("fuchsia.some.library.SomeProtocol".to_string()),
5871                        source_dictionary: Some("dict/inner".to_string()),
5872                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5873                        target_name: Some("baz".to_string()),
5874                        ..Default::default()
5875                    }),
5876                ]);
5877                decl
5878            },
5879            result = Err(ErrorList::new(vec![
5880                Error::invalid_capability(
5881                    DeclType::ExposeService,
5882                    "source",
5883                    "fuchsia.some.library.SomeProtocol"),
5884                Error::invalid_capability(
5885                    DeclType::ExposeProtocol,
5886                    "source",
5887                    "fuchsia.some.library.SomeProtocol"),
5888                Error::invalid_capability(DeclType::ExposeDirectory, "source", "dir"),
5889                Error::invalid_capability(DeclType::ExposeRunner, "source", "source_elf"),
5890                Error::invalid_capability(DeclType::ExposeResolver, "source", "source_pkg"),
5891                Error::invalid_capability(DeclType::ExposeDictionary, "source", "source_dict"),
5892                Error::invalid_capability(DeclType::ExposeConfig, "source", "source_config"),
5893                Error::invalid_capability(DeclType::ExposeProtocol, "source", "dict"),
5894            ])),
5895        },
5896
5897        test_validate_exposes_availability_service => {
5898            input = {
5899                let mut decl = generate_expose_different_source_and_availability_decl(
5900                    |source, availability, target_name|
5901                        fdecl::Expose::Service(fdecl::ExposeService {
5902                            source: Some(source),
5903                            source_name: Some("fuchsia.examples.Echo".to_string()),
5904                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5905                            target_name: Some(target_name.to_string()),
5906                            availability: Some(availability),
5907                            ..Default::default()
5908                        })
5909                );
5910                decl.capabilities = Some(vec![
5911                    fdecl::Capability::Service(fdecl::Service {
5912                        name: Some("fuchsia.examples.Echo".to_string()),
5913                        source_path: Some("/svc/fuchsia.examples.Echo".to_string()),
5914                        ..Default::default()
5915                    }),
5916                ]);
5917                decl
5918            },
5919            result = {
5920                Err(ErrorList::new(vec![
5921                    Error::availability_must_be_optional(
5922                        DeclType::ExposeService,
5923                        "availability",
5924                        Some(&"fuchsia.examples.Echo".to_string()),
5925                    ),
5926                    Error::availability_must_be_optional(
5927                        DeclType::ExposeService,
5928                        "availability",
5929                        Some(&"fuchsia.examples.Echo".to_string()),
5930                    ),
5931                ]))
5932            },
5933        },
5934        test_validate_exposes_availability_protocol => {
5935            input = {
5936                let mut decl = generate_expose_different_source_and_availability_decl(
5937                    |source, availability, target_name|
5938                        fdecl::Expose::Protocol(fdecl::ExposeProtocol {
5939                            source: Some(source),
5940                            source_name: Some("fuchsia.examples.Echo".to_string()),
5941                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5942                            target_name: Some(target_name.to_string()),
5943                            availability: Some(availability),
5944                            ..Default::default()
5945                        })
5946                );
5947                decl.capabilities = Some(vec![
5948                    fdecl::Capability::Protocol(fdecl::Protocol {
5949                        name: Some("fuchsia.examples.Echo".to_string()),
5950                        source_path: Some("/svc/fuchsia.examples.Echo".to_string()),
5951                        ..Default::default()
5952                    }),
5953                ]);
5954                decl
5955            },
5956            result = {
5957                Err(ErrorList::new(vec![
5958                    Error::availability_must_be_optional(
5959                        DeclType::ExposeProtocol,
5960                        "availability",
5961                        Some(&"fuchsia.examples.Echo".to_string()),
5962                    ),
5963                    Error::availability_must_be_optional(
5964                        DeclType::ExposeProtocol,
5965                        "availability",
5966                        Some(&"fuchsia.examples.Echo".to_string()),
5967                    ),
5968                ]))
5969            },
5970        },
5971        test_validate_exposes_availability_directory => {
5972            input = {
5973                let mut decl = generate_expose_different_source_and_availability_decl(
5974                    |source, availability, target_name|
5975                        fdecl::Expose::Directory(fdecl::ExposeDirectory {
5976                            source: Some(source),
5977                            source_name: Some("fuchsia.examples.Echo".to_string()),
5978                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5979                            target_name: Some(target_name.to_string()),
5980                            availability: Some(availability),
5981                            ..Default::default()
5982                        })
5983                );
5984                decl.capabilities = Some(vec![
5985                    fdecl::Capability::Directory(fdecl::Directory {
5986                        name: Some("fuchsia.examples.Echo".to_string()),
5987                        source_path: Some("/svc/fuchsia.examples.Echo".to_string()),
5988                        rights: Some(fio::Operations::READ_BYTES),
5989                        ..Default::default()
5990                    }),
5991                ]);
5992                decl
5993            },
5994            result = {
5995                Err(ErrorList::new(vec![
5996                    Error::availability_must_be_optional(
5997                        DeclType::ExposeDirectory,
5998                        "availability",
5999                        Some(&"fuchsia.examples.Echo".to_string()),
6000                    ),
6001                    Error::availability_must_be_optional(
6002                        DeclType::ExposeDirectory,
6003                        "availability",
6004                        Some(&"fuchsia.examples.Echo".to_string()),
6005                    ),
6006                ]))
6007            },
6008        },
6009
6010        // offers
6011        test_validate_offers_empty => {
6012            input = {
6013                let mut decl = new_component_decl();
6014                decl.offers = Some(vec![
6015                    fdecl::Offer::Service(fdecl::OfferService {
6016                        source: None,
6017                        source_name: None,
6018                        target: None,
6019                        target_name: None,
6020                        ..Default::default()
6021                    }),
6022                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
6023                        source: None,
6024                        source_name: None,
6025                        target: None,
6026                        target_name: None,
6027                        dependency_type: None,
6028                        ..Default::default()
6029                    }),
6030                    fdecl::Offer::Directory(fdecl::OfferDirectory {
6031                        source: None,
6032                        source_name: None,
6033                        target: None,
6034                        target_name: None,
6035                        rights: None,
6036                        subdir: None,
6037                        dependency_type: None,
6038                        ..Default::default()
6039                    }),
6040                    fdecl::Offer::Storage(fdecl::OfferStorage {
6041                        source_name: None,
6042                        source: None,
6043                        target: None,
6044                        target_name: None,
6045                        ..Default::default()
6046                    }),
6047                    fdecl::Offer::Runner(fdecl::OfferRunner {
6048                        source: None,
6049                        source_name: None,
6050                        target: None,
6051                        target_name: None,
6052                        ..Default::default()
6053                    }),
6054                    fdecl::Offer::Resolver(fdecl::OfferResolver {
6055                        ..Default::default()
6056                    }),
6057                    fdecl::Offer::Dictionary(fdecl::OfferDictionary {
6058                        ..Default::default()
6059                    }),
6060                ]);
6061                decl
6062            },
6063            // TODO(dgonyeo): we need to handle the availability being unset until we've soft
6064            // migrated all manifests
6065            result = Err(ErrorList::new(vec![
6066                Error::missing_field(DeclType::OfferService, "source"),
6067                Error::missing_field(DeclType::OfferService, "source_name"),
6068                Error::missing_field(DeclType::OfferService, "target"),
6069                Error::missing_field(DeclType::OfferService, "target_name"),
6070                //Error::missing_field(DeclType::OfferService, "availability"),
6071                Error::missing_field(DeclType::OfferProtocol, "source"),
6072                Error::missing_field(DeclType::OfferProtocol, "source_name"),
6073                Error::missing_field(DeclType::OfferProtocol, "target"),
6074                Error::missing_field(DeclType::OfferProtocol, "target_name"),
6075                Error::missing_field(DeclType::OfferProtocol, "dependency_type"),
6076                //Error::missing_field(DeclType::OfferProtocol, "availability"),
6077                Error::missing_field(DeclType::OfferDirectory, "source"),
6078                Error::missing_field(DeclType::OfferDirectory, "source_name"),
6079                Error::missing_field(DeclType::OfferDirectory, "target"),
6080                Error::missing_field(DeclType::OfferDirectory, "target_name"),
6081                Error::missing_field(DeclType::OfferDirectory, "dependency_type"),
6082                //Error::missing_field(DeclType::OfferDirectory, "availability"),
6083                Error::missing_field(DeclType::OfferStorage, "source"),
6084                Error::missing_field(DeclType::OfferStorage, "source_name"),
6085                Error::missing_field(DeclType::OfferStorage, "target"),
6086                Error::missing_field(DeclType::OfferStorage, "target_name"),
6087                //Error::missing_field(DeclType::OfferStorage, "availability"),
6088                Error::missing_field(DeclType::OfferRunner, "source"),
6089                Error::missing_field(DeclType::OfferRunner, "source_name"),
6090                Error::missing_field(DeclType::OfferRunner, "target"),
6091                Error::missing_field(DeclType::OfferRunner, "target_name"),
6092                //Error::missing_field(DeclType::OfferRunner, "availability"),
6093                Error::missing_field(DeclType::OfferResolver, "source"),
6094                Error::missing_field(DeclType::OfferResolver, "source_name"),
6095                Error::missing_field(DeclType::OfferResolver, "target"),
6096                Error::missing_field(DeclType::OfferResolver, "target_name"),
6097                Error::missing_field(DeclType::OfferDictionary, "source"),
6098                Error::missing_field(DeclType::OfferDictionary, "source_name"),
6099                Error::missing_field(DeclType::OfferDictionary, "target"),
6100                Error::missing_field(DeclType::OfferDictionary, "target_name"),
6101                Error::missing_field(DeclType::OfferDictionary, "dependency_type"),
6102            ])),
6103        },
6104        test_validate_offers_long_identifiers => {
6105            input = {
6106                let mut decl = new_component_decl();
6107                decl.offers = Some(vec![
6108                    fdecl::Offer::Service(fdecl::OfferService {
6109                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
6110                            name: "a".repeat(256),
6111                            collection: None,
6112                        })),
6113                        source_name: Some(format!("{}", "a".repeat(256))),
6114                        target: Some(fdecl::Ref::Child(
6115                        fdecl::ChildRef {
6116                            name: "b".repeat(256),
6117                            collection: None,
6118                        }
6119                        )),
6120                        target_name: Some(format!("{}", "b".repeat(256))),
6121                        ..Default::default()
6122                    }),
6123                    fdecl::Offer::Service(fdecl::OfferService {
6124                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
6125                        source_name: Some("a".to_string()),
6126                        target: Some(fdecl::Ref::Collection(
6127                        fdecl::CollectionRef {
6128                            name: "b".repeat(256),
6129                        }
6130                        )),
6131                        target_name: Some(format!("{}", "b".repeat(256))),
6132                        ..Default::default()
6133                    }),
6134                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
6135                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
6136                            name: "a".repeat(256),
6137                            collection: None,
6138                        })),
6139                        source_name: Some(format!("{}", "a".repeat(256))),
6140                        target: Some(fdecl::Ref::Child(
6141                        fdecl::ChildRef {
6142                            name: "b".repeat(256),
6143                            collection: None,
6144                        }
6145                        )),
6146                        target_name: Some(format!("{}", "b".repeat(256))),
6147                        dependency_type: Some(fdecl::DependencyType::Strong),
6148                        ..Default::default()
6149                    }),
6150                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
6151                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
6152                        source_name: Some("a".to_string()),
6153                        target: Some(fdecl::Ref::Collection(
6154                        fdecl::CollectionRef {
6155                            name: "b".repeat(256),
6156                        }
6157                        )),
6158                        target_name: Some(format!("{}", "b".repeat(256))),
6159                        dependency_type: Some(fdecl::DependencyType::Weak),
6160                        ..Default::default()
6161                    }),
6162                    fdecl::Offer::Directory(fdecl::OfferDirectory {
6163                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
6164                            name: "a".repeat(256),
6165                            collection: None,
6166                        })),
6167                        source_name: Some(format!("{}", "a".repeat(256))),
6168                        target: Some(fdecl::Ref::Child(
6169                        fdecl::ChildRef {
6170                            name: "b".repeat(256),
6171                            collection: None,
6172                        }
6173                        )),
6174                        target_name: Some(format!("{}", "b".repeat(256))),
6175                        rights: Some(fio::Operations::CONNECT),
6176                        subdir: None,
6177                        dependency_type: Some(fdecl::DependencyType::Strong),
6178                        ..Default::default()
6179                    }),
6180                    fdecl::Offer::Directory(fdecl::OfferDirectory {
6181                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
6182                        source_name: Some("a".to_string()),
6183                        target: Some(fdecl::Ref::Collection(
6184                        fdecl::CollectionRef {
6185                            name: "b".repeat(256),
6186                        }
6187                        )),
6188                        target_name: Some(format!("{}", "b".repeat(256))),
6189                        rights: Some(fio::Operations::CONNECT),
6190                        subdir: None,
6191                        dependency_type: Some(fdecl::DependencyType::Weak),
6192                        ..Default::default()
6193                    }),
6194                    fdecl::Offer::Storage(fdecl::OfferStorage {
6195                        source_name: Some("data".to_string()),
6196                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
6197                        target: Some(fdecl::Ref::Child(
6198                            fdecl::ChildRef {
6199                                name: "b".repeat(256),
6200                                collection: None,
6201                            }
6202                        )),
6203                        target_name: Some("data".to_string()),
6204                        ..Default::default()
6205                    }),
6206                    fdecl::Offer::Storage(fdecl::OfferStorage {
6207                        source_name: Some("data".to_string()),
6208                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
6209                        target: Some(fdecl::Ref::Collection(
6210                            fdecl::CollectionRef { name: "b".repeat(256) }
6211                        )),
6212                        target_name: Some("data".to_string()),
6213                        ..Default::default()
6214                    }),
6215                    fdecl::Offer::Runner(fdecl::OfferRunner {
6216                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
6217                            name: "a".repeat(256),
6218                            collection: None,
6219                        })),
6220                        source_name: Some("b".repeat(256)),
6221                        target: Some(fdecl::Ref::Collection(
6222                        fdecl::CollectionRef {
6223                            name: "c".repeat(256),
6224                        }
6225                        )),
6226                        target_name: Some("d".repeat(256)),
6227                        ..Default::default()
6228                    }),
6229                    fdecl::Offer::Resolver(fdecl::OfferResolver {
6230                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
6231                            name: "a".repeat(256),
6232                            collection: None,
6233                        })),
6234                        source_name: Some("b".repeat(256)),
6235                        target: Some(fdecl::Ref::Collection(
6236                            fdecl::CollectionRef {
6237                                name: "c".repeat(256),
6238                            }
6239                        )),
6240                        target_name: Some("d".repeat(256)),
6241                        ..Default::default()
6242                    }),
6243                    fdecl::Offer::Dictionary(fdecl::OfferDictionary {
6244                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
6245                            name: "a".repeat(256),
6246                            collection: None,
6247                        })),
6248                        source_name: Some("b".repeat(256)),
6249                        target: Some(fdecl::Ref::Collection(
6250                            fdecl::CollectionRef {
6251                                name: "c".repeat(256),
6252                            }
6253                        )),
6254                        target_name: Some("d".repeat(256)),
6255                        dependency_type: Some(fdecl::DependencyType::Strong),
6256                        ..Default::default()
6257                    }),
6258                ]);
6259                decl
6260            },
6261            result = Err(ErrorList::new(vec![
6262                Error::field_too_long(DeclType::OfferService, "source.child.name"),
6263                Error::field_too_long(DeclType::OfferService, "source_name"),
6264                Error::field_too_long(DeclType::OfferService, "target.child.name"),
6265                Error::field_too_long(DeclType::OfferService, "target_name"),
6266                Error::field_too_long(DeclType::OfferService, "target.collection.name"),
6267                Error::field_too_long(DeclType::OfferService, "target_name"),
6268                Error::field_too_long(DeclType::OfferProtocol, "source.child.name"),
6269                Error::field_too_long(DeclType::OfferProtocol, "source_name"),
6270                Error::field_too_long(DeclType::OfferProtocol, "target.child.name"),
6271                Error::field_too_long(DeclType::OfferProtocol, "target_name"),
6272                Error::field_too_long(DeclType::OfferProtocol, "target.collection.name"),
6273                Error::field_too_long(DeclType::OfferProtocol, "target_name"),
6274                Error::field_too_long(DeclType::OfferDirectory, "source.child.name"),
6275                Error::field_too_long(DeclType::OfferDirectory, "source_name"),
6276                Error::field_too_long(DeclType::OfferDirectory, "target.child.name"),
6277                Error::field_too_long(DeclType::OfferDirectory, "target_name"),
6278                Error::field_too_long(DeclType::OfferDirectory, "target.collection.name"),
6279                Error::field_too_long(DeclType::OfferDirectory, "target_name"),
6280                Error::field_too_long(DeclType::OfferStorage, "target.child.name"),
6281                Error::field_too_long(DeclType::OfferStorage, "target.collection.name"),
6282                Error::field_too_long(DeclType::OfferRunner, "source.child.name"),
6283                Error::field_too_long(DeclType::OfferRunner, "source_name"),
6284                Error::field_too_long(DeclType::OfferRunner, "target.collection.name"),
6285                Error::field_too_long(DeclType::OfferRunner, "target_name"),
6286                Error::field_too_long(DeclType::OfferResolver, "source.child.name"),
6287                Error::field_too_long(DeclType::OfferResolver, "source_name"),
6288                Error::field_too_long(DeclType::OfferResolver, "target.collection.name"),
6289                Error::field_too_long(DeclType::OfferResolver, "target_name"),
6290                Error::field_too_long(DeclType::OfferDictionary, "source.child.name"),
6291                Error::field_too_long(DeclType::OfferDictionary, "source_name"),
6292                Error::field_too_long(DeclType::OfferDictionary, "target.collection.name"),
6293                Error::field_too_long(DeclType::OfferDictionary, "target_name"),
6294            ])),
6295        },
6296        test_validate_offers_extraneous => {
6297            input = {
6298                let mut decl = new_component_decl();
6299                decl.offers = Some(vec![
6300                    fdecl::Offer::Service(fdecl::OfferService {
6301                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
6302                            name: "logger".to_string(),
6303                            collection: Some("modular".to_string()),
6304                        })),
6305                        source_name: Some("fuchsia.logger.Log".to_string()),
6306                        target: Some(fdecl::Ref::Child(
6307                            fdecl::ChildRef {
6308                                name: "netstack".to_string(),
6309                                collection: Some("modular".to_string()),
6310                            }
6311                        )),
6312                        target_name: Some("fuchsia.logger.Log".to_string()),
6313                        ..Default::default()
6314                    }),
6315                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
6316                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
6317                            name: "logger".to_string(),
6318                            collection: Some("modular".to_string()),
6319                        })),
6320                        source_name: Some("fuchsia.logger.Log".to_string()),
6321                        target: Some(fdecl::Ref::Child(
6322                            fdecl::ChildRef {
6323                                name: "netstack".to_string(),
6324                                collection: Some("modular".to_string()),
6325                            }
6326                        )),
6327                        target_name: Some("fuchsia.logger.Log".to_string()),
6328                        dependency_type: Some(fdecl::DependencyType::Strong),
6329                        ..Default::default()
6330                    }),
6331                    fdecl::Offer::Directory(fdecl::OfferDirectory {
6332                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
6333                            name: "logger".to_string(),
6334                            collection: Some("modular".to_string()),
6335                        })),
6336                        source_name: Some("assets".to_string()),
6337                        target: Some(fdecl::Ref::Child(
6338                            fdecl::ChildRef {
6339                                name: "netstack".to_string(),
6340                                collection: Some("modular".to_string()),
6341                            }
6342                        )),
6343                        target_name: Some("assets".to_string()),
6344                        rights: Some(fio::Operations::CONNECT),
6345                        subdir: None,
6346                        dependency_type: Some(fdecl::DependencyType::Weak),
6347                        ..Default::default()
6348                    }),
6349                    fdecl::Offer::Storage(fdecl::OfferStorage {
6350                        source_name: Some("data".to_string()),
6351                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{ })),
6352                        target: Some(fdecl::Ref::Child(
6353                            fdecl::ChildRef {
6354                                name: "netstack".to_string(),
6355                                collection: Some("modular".to_string()),
6356                            }
6357                        )),
6358                        target_name: Some("data".to_string()),
6359                        ..Default::default()
6360                    }),
6361                    fdecl::Offer::Runner(fdecl::OfferRunner {
6362                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
6363                            name: "logger".to_string(),
6364                            collection: Some("modular".to_string()),
6365                        })),
6366                        source_name: Some("elf".to_string()),
6367                        target: Some(fdecl::Ref::Child(
6368                            fdecl::ChildRef {
6369                                name: "netstack".to_string(),
6370                                collection: Some("modular".to_string()),
6371                            }
6372                        )),
6373                        target_name: Some("elf".to_string()),
6374                        ..Default::default()
6375                    }),
6376                    fdecl::Offer::Resolver(fdecl::OfferResolver {
6377                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
6378                            name: "logger".to_string(),
6379                            collection: Some("modular".to_string()),
6380                        })),
6381                        source_name: Some("pkg".to_string()),
6382                        target: Some(fdecl::Ref::Child(
6383                            fdecl::ChildRef {
6384                                name: "netstack".to_string(),
6385                                collection: Some("modular".to_string()),
6386                            }
6387                        )),
6388                        target_name: Some("pkg".to_string()),
6389                        ..Default::default()
6390                    }),
6391                    fdecl::Offer::Dictionary(fdecl::OfferDictionary {
6392                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
6393                            name: "logger".to_string(),
6394                            collection: Some("modular".to_string()),
6395                        })),
6396                        source_name: Some("dict".to_string()),
6397                        target: Some(fdecl::Ref::Child(
6398                            fdecl::ChildRef {
6399                                name: "netstack".to_string(),
6400                                collection: Some("modular".to_string()),
6401                            }
6402                        )),
6403                        target_name: Some("dict".to_string()),
6404                        dependency_type: Some(fdecl::DependencyType::Strong),
6405                        ..Default::default()
6406                    }),
6407                ]);
6408                decl.capabilities = Some(vec![
6409                    fdecl::Capability::Protocol(fdecl::Protocol {
6410                        name: Some("fuchsia.logger.Log".to_string()),
6411                        source_path: Some("/svc/logger".to_string()),
6412                        ..Default::default()
6413                    }),
6414                    fdecl::Capability::Directory(fdecl::Directory {
6415                        name: Some("assets".to_string()),
6416                        source_path: Some("/data/assets".to_string()),
6417                        rights: Some(fio::Operations::CONNECT),
6418                        ..Default::default()
6419                    }),
6420                ]);
6421                decl
6422            },
6423            result = Err(ErrorList::new(vec![
6424                Error::extraneous_field(DeclType::OfferService, "source.child.collection"),
6425                Error::extraneous_field(DeclType::OfferService, "target.child.collection"),
6426                Error::extraneous_field(DeclType::OfferProtocol, "source.child.collection"),
6427                Error::extraneous_field(DeclType::OfferProtocol, "target.child.collection"),
6428                Error::extraneous_field(DeclType::OfferDirectory, "source.child.collection"),
6429                Error::extraneous_field(DeclType::OfferDirectory, "target.child.collection"),
6430                Error::extraneous_field(DeclType::OfferStorage, "target.child.collection"),
6431                Error::extraneous_field(DeclType::OfferRunner, "source.child.collection"),
6432                Error::extraneous_field(DeclType::OfferRunner, "target.child.collection"),
6433                Error::extraneous_field(DeclType::OfferResolver, "source.child.collection"),
6434                Error::extraneous_field(DeclType::OfferResolver, "target.child.collection"),
6435                Error::extraneous_field(DeclType::OfferDictionary, "source.child.collection"),
6436                Error::extraneous_field(DeclType::OfferDictionary, "target.child.collection"),
6437            ])),
6438        },
6439        test_validate_offers_invalid_filtered_service_fields => {
6440            input = {
6441                let mut decl = new_component_decl();
6442                decl.offers = Some(vec![
6443                    fdecl::Offer::Service(fdecl::OfferService {
6444                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
6445                        source_name: Some("fuchsia.logger.Log".to_string()),
6446                        target: Some(fdecl::Ref::Child(
6447                            fdecl::ChildRef {
6448                                name: "logger".to_string(),
6449                                collection: None,
6450                            }
6451                        )),
6452                        target_name: Some("fuchsia.logger.Log".to_string()),
6453                        source_instance_filter: Some(vec![]),
6454                        ..Default::default()
6455                    }),
6456                    fdecl::Offer::Service(fdecl::OfferService {
6457                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
6458                        source_name: Some("fuchsia.logger.Log".to_string()),
6459                        target: Some(fdecl::Ref::Child(
6460                            fdecl::ChildRef {
6461                                name: "logger".to_string(),
6462                                collection: None,
6463                            }
6464                        )),
6465                        target_name: Some("fuchsia.logger.Log2".to_string()),
6466                        source_instance_filter: Some(vec!["^badname".to_string()]),
6467                        ..Default::default()
6468                    }),
6469                    fdecl::Offer::Service(fdecl::OfferService {
6470                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
6471                        source_name: Some("fuchsia.logger.Log".to_string()),
6472                        target: Some(fdecl::Ref::Child(
6473                            fdecl::ChildRef {
6474                                name: "logger".to_string(),
6475                                collection: None,
6476                            }
6477                        )),
6478                        target_name: Some("fuchsia.logger.Log1".to_string()),
6479                        renamed_instances: Some(vec![fdecl::NameMapping{source_name: "a".to_string(), target_name: "b".to_string()}, fdecl::NameMapping{source_name: "c".to_string(), target_name: "b".to_string()}]),
6480                        ..Default::default()
6481                    }),
6482                    fdecl::Offer::Service(fdecl::OfferService {
6483                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
6484                        source_name: Some("fuchsia.logger.Log".to_string()),
6485                        target: Some(fdecl::Ref::Child(
6486                            fdecl::ChildRef {
6487                                name: "logger".to_string(),
6488                                collection: None,
6489                            }
6490                        )),
6491                        target_name: Some("fuchsia.logger.Log3".to_string()),
6492                        renamed_instances: Some(vec![
6493                            fdecl::NameMapping {
6494                                source_name: "^badname".to_string(),
6495                                target_name: "^badname".to_string(),
6496                            }
6497                        ]),
6498                        ..Default::default()
6499                    })
6500                ]);
6501                decl.children = Some(vec![
6502                    fdecl::Child {
6503                        name: Some("logger".to_string()),
6504                        url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
6505                        startup: Some(fdecl::StartupMode::Lazy),
6506                        on_terminate: None,
6507                        environment: None,
6508                        ..Default::default()
6509                    },
6510                ]);
6511                decl
6512            },
6513            result = Err(ErrorList::new(vec![
6514                Error::invalid_field(DeclType::OfferService, "source_instance_filter"),
6515                Error::invalid_field(DeclType::OfferService, "source_instance_filter"),
6516                Error::invalid_field(DeclType::OfferService, "renamed_instances"),
6517                Error::invalid_field(DeclType::OfferService, "renamed_instances.source_name"),
6518                Error::invalid_field(DeclType::OfferService, "renamed_instances.target_name"),
6519            ])),
6520        },
6521        test_validate_offers_invalid_identifiers => {
6522            input = {
6523                let mut decl = new_component_decl();
6524                decl.offers = Some(vec![
6525                    fdecl::Offer::Service(fdecl::OfferService {
6526                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
6527                            name: "^bad".to_string(),
6528                            collection: None,
6529                        })),
6530                        source_name: Some("foo/".to_string()),
6531                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
6532                            name: "%bad".to_string(),
6533                            collection: None,
6534                        })),
6535                        target_name: Some("/".to_string()),
6536                        ..Default::default()
6537                    }),
6538                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
6539                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
6540                            name: "^bad".to_string(),
6541                            collection: None,
6542                        })),
6543                        source_name: Some("foo/".to_string()),
6544                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
6545                            name: "%bad".to_string(),
6546                            collection: None,
6547                        })),
6548                        target_name: Some("/".to_string()),
6549                        dependency_type: Some(fdecl::DependencyType::Strong),
6550                        ..Default::default()
6551                    }),
6552                    fdecl::Offer::Directory(fdecl::OfferDirectory {
6553                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
6554                            name: "^bad".to_string(),
6555                            collection: None,
6556                        })),
6557                        source_name: Some("foo/".to_string()),
6558                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
6559                            name: "%bad".to_string(),
6560                            collection: None,
6561                        })),
6562                        target_name: Some("/".to_string()),
6563                        rights: Some(fio::Operations::CONNECT),
6564                        subdir: Some("/foo".to_string()),
6565                        dependency_type: Some(fdecl::DependencyType::Strong),
6566                        ..Default::default()
6567                    }),
6568                    fdecl::Offer::Runner(fdecl::OfferRunner {
6569                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
6570                            name: "^bad".to_string(),
6571                            collection: None,
6572                        })),
6573                        source_name: Some("/path".to_string()),
6574                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
6575                            name: "%bad".to_string(),
6576                            collection: None,
6577                        })),
6578                        target_name: Some("elf!".to_string()),
6579                        ..Default::default()
6580                    }),
6581                    fdecl::Offer::Resolver(fdecl::OfferResolver {
6582                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
6583                            name: "^bad".to_string(),
6584                            collection: None,
6585                        })),
6586                        source_name: Some("/path".to_string()),
6587                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
6588                            name: "%bad".to_string(),
6589                            collection: None,
6590                        })),
6591                        target_name: Some("pkg!".to_string()),
6592                        ..Default::default()
6593                    }),
6594                    fdecl::Offer::Dictionary(fdecl::OfferDictionary {
6595                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
6596                            name: "^bad".to_string(),
6597                            collection: None,
6598                        })),
6599                        source_name: Some("/path".to_string()),
6600                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
6601                            name: "%bad".to_string(),
6602                            collection: None,
6603                        })),
6604                        target_name: Some("pkg!".to_string()),
6605                        dependency_type: Some(fdecl::DependencyType::Strong),
6606                        ..Default::default()
6607                    }),
6608                ]);
6609                decl
6610            },
6611            result = Err(ErrorList::new(vec![
6612                Error::invalid_field(DeclType::OfferService, "source.child.name"),
6613                Error::invalid_field(DeclType::OfferService, "source_name"),
6614                Error::invalid_field(DeclType::OfferService, "target.child.name"),
6615                Error::invalid_field(DeclType::OfferService, "target_name"),
6616                Error::invalid_field(DeclType::OfferProtocol, "source.child.name"),
6617                Error::invalid_field(DeclType::OfferProtocol, "source_name"),
6618                Error::invalid_field(DeclType::OfferProtocol, "target.child.name"),
6619                Error::invalid_field(DeclType::OfferProtocol, "target_name"),
6620                Error::invalid_field(DeclType::OfferDirectory, "source.child.name"),
6621                Error::invalid_field(DeclType::OfferDirectory, "source_name"),
6622                Error::invalid_field(DeclType::OfferDirectory, "target.child.name"),
6623                Error::invalid_field(DeclType::OfferDirectory, "target_name"),
6624                Error::invalid_field(DeclType::OfferDirectory, "subdir"),
6625                Error::invalid_field(DeclType::OfferRunner, "source.child.name"),
6626                Error::invalid_field(DeclType::OfferRunner, "source_name"),
6627                Error::invalid_field(DeclType::OfferRunner, "target.child.name"),
6628                Error::invalid_field(DeclType::OfferRunner, "target_name"),
6629                Error::invalid_field(DeclType::OfferResolver, "source.child.name"),
6630                Error::invalid_field(DeclType::OfferResolver, "source_name"),
6631                Error::invalid_field(DeclType::OfferResolver, "target.child.name"),
6632                Error::invalid_field(DeclType::OfferResolver, "target_name"),
6633                Error::invalid_field(DeclType::OfferDictionary, "source.child.name"),
6634                Error::invalid_field(DeclType::OfferDictionary, "source_name"),
6635                Error::invalid_field(DeclType::OfferDictionary, "target.child.name"),
6636                Error::invalid_field(DeclType::OfferDictionary, "target_name"),
6637            ])),
6638        },
6639        test_validate_offers_target_equals_source => {
6640            input = {
6641                let mut decl = new_component_decl();
6642                decl.offers = Some(vec![
6643                    fdecl::Offer::Service(fdecl::OfferService {
6644                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
6645                            name: "logger".to_string(),
6646                            collection: None,
6647                        })),
6648                        source_name: Some("logger".to_string()),
6649                        target: Some(fdecl::Ref::Child(
6650                        fdecl::ChildRef {
6651                            name: "logger".to_string(),
6652                            collection: None,
6653                        }
6654                        )),
6655                        target_name: Some("logger".to_string()),
6656                        ..Default::default()
6657                    }),
6658                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
6659                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
6660                            name: "logger".to_string(),
6661                            collection: None,
6662                        })),
6663                        source_name: Some("legacy_logger".to_string()),
6664                        target: Some(fdecl::Ref::Child(
6665                        fdecl::ChildRef {
6666                            name: "logger".to_string(),
6667                            collection: None,
6668                        }
6669                        )),
6670                        target_name: Some("weak_legacy_logger".to_string()),
6671                        dependency_type: Some(fdecl::DependencyType::Weak),
6672                        ..Default::default()
6673                    }),
6674                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
6675                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
6676                            name: "logger".to_string(),
6677                            collection: None,
6678                        })),
6679                        source_name: Some("legacy_logger".to_string()),
6680                        target: Some(fdecl::Ref::Child(
6681                        fdecl::ChildRef {
6682                            name: "logger".to_string(),
6683                            collection: None,
6684                        }
6685                        )),
6686                        target_name: Some("strong_legacy_logger".to_string()),
6687                        dependency_type: Some(fdecl::DependencyType::Strong),
6688                        ..Default::default()
6689                    }),
6690                    fdecl::Offer::Directory(fdecl::OfferDirectory {
6691                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
6692                            name: "logger".to_string(),
6693                            collection: None,
6694                        })),
6695                        source_name: Some("assets".to_string()),
6696                        target: Some(fdecl::Ref::Child(
6697                        fdecl::ChildRef {
6698                            name: "logger".to_string(),
6699                            collection: None,
6700                        }
6701                        )),
6702                        target_name: Some("assets".to_string()),
6703                        rights: Some(fio::Operations::CONNECT),
6704                        subdir: None,
6705                        dependency_type: Some(fdecl::DependencyType::Strong),
6706                        ..Default::default()
6707                    }),
6708                    fdecl::Offer::Runner(fdecl::OfferRunner {
6709                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
6710                            name: "logger".to_string(),
6711                            collection: None,
6712                        })),
6713                        source_name: Some("web".to_string()),
6714                        target: Some(fdecl::Ref::Child(
6715                        fdecl::ChildRef {
6716                            name: "logger".to_string(),
6717                            collection: None,
6718                        }
6719                        )),
6720                        target_name: Some("web".to_string()),
6721                        ..Default::default()
6722                    }),
6723                    fdecl::Offer::Resolver(fdecl::OfferResolver {
6724                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
6725                            name: "logger".to_string(),
6726                            collection: None,
6727                        })),
6728                        source_name: Some("pkg".to_string()),
6729                        target: Some(fdecl::Ref::Child(
6730                        fdecl::ChildRef {
6731                            name: "logger".to_string(),
6732                            collection: None,
6733                        }
6734                        )),
6735                        target_name: Some("pkg".to_string()),
6736                        ..Default::default()
6737                    }),
6738                    fdecl::Offer::Dictionary(fdecl::OfferDictionary {
6739                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
6740                            name: "logger".to_string(),
6741                            collection: None,
6742                        })),
6743                        source_name: Some("dict".to_string()),
6744                        target: Some(fdecl::Ref::Child(
6745                        fdecl::ChildRef {
6746                            name: "logger".to_string(),
6747                            collection: None,
6748                        }
6749                        )),
6750                        target_name: Some("dict".to_string()),
6751                        dependency_type: Some(fdecl::DependencyType::Strong),
6752                        ..Default::default()
6753                    }),
6754                ]);
6755                decl.children = Some(vec![fdecl::Child{
6756                    name: Some("logger".to_string()),
6757                    url: Some("fuchsia-pkg://fuchsia.com/logger#meta/logger.cm".to_string()),
6758                    startup: Some(fdecl::StartupMode::Lazy),
6759                    on_terminate: None,
6760                    environment: None,
6761                    ..Default::default()
6762                }]);
6763                decl
6764            },
6765            result = Err(ErrorList::new(vec![
6766                Error::dependency_cycle("{{child logger -> child logger}}"),
6767            ])),
6768        },
6769        test_validate_offers_storage_target_equals_source => {
6770            input = fdecl::Component {
6771                offers: Some(vec![
6772                    fdecl::Offer::Storage(fdecl::OfferStorage {
6773                        source_name: Some("data".to_string()),
6774                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef { })),
6775                        target: Some(fdecl::Ref::Child(
6776                            fdecl::ChildRef {
6777                                name: "logger".to_string(),
6778                                collection: None,
6779                            }
6780                        )),
6781                        target_name: Some("data".to_string()),
6782                        ..Default::default()
6783                    })
6784                ]),
6785                capabilities: Some(vec![
6786                    fdecl::Capability::Storage(fdecl::Storage {
6787                        name: Some("data".to_string()),
6788                        backing_dir: Some("minfs".to_string()),
6789                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
6790                            name: "logger".to_string(),
6791                            collection: None,
6792                        })),
6793                        subdir: None,
6794                        storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker),
6795                        ..Default::default()
6796                    }),
6797                ]),
6798                children: Some(vec![
6799                    fdecl::Child {
6800                        name: Some("logger".to_string()),
6801                        url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
6802                        startup: Some(fdecl::StartupMode::Lazy),
6803                        on_terminate: None,
6804                        environment: None,
6805                        ..Default::default()
6806                    },
6807                ]),
6808                ..new_component_decl()
6809            },
6810            result = Err(ErrorList::new(vec![
6811                Error::dependency_cycle("{{child logger -> capability data -> child logger}}"),
6812            ])),
6813        },
6814        test_validate_offers_invalid_child => {
6815            input = {
6816                let mut decl = new_component_decl();
6817                decl.offers = Some(vec![
6818                    fdecl::Offer::Service(fdecl::OfferService {
6819                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
6820                            name: "logger".to_string(),
6821                            collection: None,
6822                        })),
6823                        source_name: Some("fuchsia.logger.Log".to_string()),
6824                        target: Some(fdecl::Ref::Child(
6825                        fdecl::ChildRef {
6826                            name: "netstack".to_string(),
6827                            collection: None,
6828                        }
6829                        )),
6830                        target_name: Some("fuchsia.logger.Log".to_string()),
6831                        ..Default::default()
6832                    }),
6833                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
6834                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
6835                            name: "logger".to_string(),
6836                            collection: None,
6837                        })),
6838                        source_name: Some("fuchsia.logger.LegacyLog".to_string()),
6839                        target: Some(fdecl::Ref::Child(
6840                        fdecl::ChildRef {
6841                            name: "netstack".to_string(),
6842                            collection: None,
6843                        }
6844                        )),
6845                        target_name: Some("fuchsia.logger.LegacyLog".to_string()),
6846                        dependency_type: Some(fdecl::DependencyType::Strong),
6847                        ..Default::default()
6848                    }),
6849                    fdecl::Offer::Directory(fdecl::OfferDirectory {
6850                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
6851                            name: "logger".to_string(),
6852                            collection: None,
6853                        })),
6854                        source_name: Some("assets".to_string()),
6855                        target: Some(fdecl::Ref::Collection(
6856                        fdecl::CollectionRef { name: "modular".to_string() }
6857                        )),
6858                        target_name: Some("assets".to_string()),
6859                        rights: Some(fio::Operations::CONNECT),
6860                        subdir: None,
6861                        dependency_type: Some(fdecl::DependencyType::Weak),
6862                        ..Default::default()
6863                    }),
6864                ]);
6865                decl.capabilities = Some(vec![
6866                    fdecl::Capability::Storage(fdecl::Storage {
6867                        name: Some("memfs".to_string()),
6868                        backing_dir: Some("memfs".to_string()),
6869                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
6870                            name: "logger".to_string(),
6871                            collection: None,
6872                        })),
6873                        subdir: None,
6874                        storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker),
6875                        ..Default::default()
6876                    }),
6877                ]);
6878                decl.children = Some(vec![
6879                    fdecl::Child {
6880                        name: Some("netstack".to_string()),
6881                        url: Some("fuchsia-pkg://fuchsia.com/netstack/stable#meta/netstack.cm".to_string()),
6882                        startup: Some(fdecl::StartupMode::Lazy),
6883                        on_terminate: None,
6884                        environment: None,
6885                        ..Default::default()
6886                    },
6887                ]);
6888                decl.collections = Some(vec![
6889                    fdecl::Collection {
6890                        name: Some("modular".to_string()),
6891                        durability: Some(fdecl::Durability::Transient),
6892                        environment: None,
6893                        allowed_offers: Some(fdecl::AllowedOffers::StaticAndDynamic),
6894                        allow_long_names: None,
6895                        ..Default::default()
6896                    },
6897                ]);
6898                decl
6899            },
6900            result = Err(ErrorList::new(vec![
6901                Error::invalid_child(DeclType::Storage, "source", "logger"),
6902                Error::invalid_child(DeclType::OfferService, "source", "logger"),
6903                Error::invalid_child(DeclType::OfferProtocol, "source", "logger"),
6904                Error::invalid_child(DeclType::OfferDirectory, "source", "logger"),
6905            ])),
6906        },
6907        test_validate_offers_invalid_source_capability => {
6908            input = {
6909                fdecl::Component {
6910                    offers: Some(vec![
6911                        fdecl::Offer::Protocol(fdecl::OfferProtocol {
6912                            source: Some(fdecl::Ref::Capability(fdecl::CapabilityRef {
6913                                name: "this-storage-doesnt-exist".to_string(),
6914                            })),
6915                            source_name: Some("fuchsia.sys2.StorageAdmin".to_string()),
6916                            target: Some(fdecl::Ref::Child(
6917                            fdecl::ChildRef {
6918                                name: "netstack".to_string(),
6919                                collection: None,
6920                            }
6921                            )),
6922                            target_name: Some("fuchsia.sys2.StorageAdmin".to_string()),
6923                            dependency_type: Some(fdecl::DependencyType::Strong),
6924                            ..Default::default()
6925                        }),
6926                    ]),
6927                    ..new_component_decl()
6928                }
6929            },
6930            result = Err(ErrorList::new(vec![
6931                Error::invalid_capability(DeclType::OfferProtocol, "source", "this-storage-doesnt-exist"),
6932                Error::invalid_child(DeclType::OfferProtocol, "target", "netstack"),
6933            ])),
6934        },
6935        test_validate_offers_target => {
6936            input = {
6937                let mut decl = new_component_decl();
6938                decl.offers = Some(vec![
6939                    fdecl::Offer::Service(fdecl::OfferService {
6940                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
6941                            name: "modular".into()
6942                        })),
6943                        source_name: Some("logger".to_string()),
6944                        target: Some(fdecl::Ref::Child(
6945                        fdecl::ChildRef {
6946                            name: "netstack".to_string(),
6947                            collection: None,
6948                        }
6949                        )),
6950                        target_name: Some("fuchsia.logger.Log".to_string()),
6951                        ..Default::default()
6952                    }),
6953                    fdecl::Offer::Service(fdecl::OfferService {
6954                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
6955                            name: "modular".into()
6956                        })),
6957                        source_name: Some("logger".to_string()),
6958                        target: Some(fdecl::Ref::Child(
6959                        fdecl::ChildRef {
6960                            name: "netstack".to_string(),
6961                            collection: None,
6962                        }
6963                        )),
6964                        target_name: Some("fuchsia.logger.Log".to_string()),
6965                        ..Default::default()
6966                    }),
6967                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
6968                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
6969                        source_name: Some("fuchsia.logger.LegacyLog".to_string()),
6970                        target: Some(fdecl::Ref::Child(
6971                        fdecl::ChildRef {
6972                            name: "netstack".to_string(),
6973                            collection: None,
6974                        }
6975                        )),
6976                        target_name: Some("fuchsia.logger.LegacyLog".to_string()),
6977                        dependency_type: Some(fdecl::DependencyType::Strong),
6978                        ..Default::default()
6979                    }),
6980                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
6981                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
6982                        source_name: Some("fuchsia.logger.LegacyLog".to_string()),
6983                        target: Some(fdecl::Ref::Child(
6984                        fdecl::ChildRef {
6985                            name: "netstack".to_string(),
6986                            collection: None,
6987                        }
6988                        )),
6989                        target_name: Some("fuchsia.logger.LegacyLog".to_string()),
6990                        dependency_type: Some(fdecl::DependencyType::Strong),
6991                        ..Default::default()
6992                    }),
6993                    fdecl::Offer::Directory(fdecl::OfferDirectory {
6994                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
6995                        source_name: Some("assets".to_string()),
6996                        target: Some(fdecl::Ref::Collection(
6997                        fdecl::CollectionRef { name: "modular".to_string() }
6998                        )),
6999                        target_name: Some("assets".to_string()),
7000                        rights: Some(fio::Operations::CONNECT),
7001                        subdir: None,
7002                        dependency_type: Some(fdecl::DependencyType::Strong),
7003                        ..Default::default()
7004                    }),
7005                    fdecl::Offer::Directory(fdecl::OfferDirectory {
7006                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
7007                        source_name: Some("assets".to_string()),
7008                        target: Some(fdecl::Ref::Collection(
7009                        fdecl::CollectionRef { name: "modular".to_string() }
7010                        )),
7011                        target_name: Some("assets".to_string()),
7012                        rights: Some(fio::Operations::CONNECT),
7013                        subdir: None,
7014                        dependency_type: Some(fdecl::DependencyType::Weak),
7015                        ..Default::default()
7016                    }),
7017                    fdecl::Offer::Storage(fdecl::OfferStorage {
7018                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
7019                        source_name: Some("data".to_string()),
7020                        target: Some(fdecl::Ref::Collection(
7021                        fdecl::CollectionRef { name: "modular".to_string() }
7022                        )),
7023                        target_name: Some("data".to_string()),
7024                        ..Default::default()
7025                    }),
7026                    fdecl::Offer::Storage(fdecl::OfferStorage {
7027                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
7028                        source_name: Some("data".to_string()),
7029                        target: Some(fdecl::Ref::Collection(
7030                        fdecl::CollectionRef { name: "modular".to_string() }
7031                        )),
7032                        target_name: Some("data".to_string()),
7033                        ..Default::default()
7034                    }),
7035                    fdecl::Offer::Runner(fdecl::OfferRunner {
7036                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
7037                        source_name: Some("elf".to_string()),
7038                        target: Some(fdecl::Ref::Collection(
7039                        fdecl::CollectionRef { name: "modular".to_string() }
7040                        )),
7041                        target_name: Some("duplicated".to_string()),
7042                        ..Default::default()
7043                    }),
7044                    fdecl::Offer::Runner(fdecl::OfferRunner {
7045                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
7046                        source_name: Some("elf".to_string()),
7047                        target: Some(fdecl::Ref::Collection(
7048                        fdecl::CollectionRef { name: "modular".to_string() }
7049                        )),
7050                        target_name: Some("duplicated".to_string()),
7051                        ..Default::default()
7052                    }),
7053                    fdecl::Offer::Resolver(fdecl::OfferResolver {
7054                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
7055                        source_name: Some("pkg".to_string()),
7056                        target: Some(fdecl::Ref::Collection(
7057                        fdecl::CollectionRef { name: "modular".to_string() }
7058                        )),
7059                        target_name: Some("duplicated".to_string()),
7060                        ..Default::default()
7061                    }),
7062                    fdecl::Offer::EventStream(fdecl::OfferEventStream {
7063                        source_name: Some("started".to_string()),
7064                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
7065                        target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "netstack".to_string(), collection: None})),
7066                        target_name: Some("started".to_string()),
7067                        ..Default::default()
7068                    }),
7069                    fdecl::Offer::EventStream(fdecl::OfferEventStream {
7070                        source_name: Some("started".to_string()),
7071                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
7072                        target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "netstack".to_string(), collection: None})),
7073                        target_name: Some("started".to_string()),
7074                        ..Default::default()
7075                    }),
7076                    fdecl::Offer::Dictionary(fdecl::OfferDictionary {
7077                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
7078                        source_name: Some("a".to_string()),
7079                        target: Some(fdecl::Ref::Collection(
7080                            fdecl::CollectionRef { name: "modular".to_string() }
7081                        )),
7082                        target_name: Some("dict".to_string()),
7083                        dependency_type: Some(fdecl::DependencyType::Strong),
7084                        ..Default::default()
7085                    }),
7086                    fdecl::Offer::Dictionary(fdecl::OfferDictionary {
7087                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
7088                        source_name: Some("b".to_string()),
7089                        target: Some(fdecl::Ref::Collection(
7090                            fdecl::CollectionRef { name: "modular".to_string() }
7091                        )),
7092                        target_name: Some("dict".to_string()),
7093                        dependency_type: Some(fdecl::DependencyType::Strong),
7094                        ..Default::default()
7095                    }),
7096                ]);
7097                decl.children = Some(vec![
7098                    fdecl::Child{
7099                        name: Some("netstack".to_string()),
7100                        url: Some("fuchsia-pkg://fuchsia.com/netstack/stable#meta/netstack.cm".to_string()),
7101                        startup: Some(fdecl::StartupMode::Eager),
7102                        on_terminate: None,
7103                        environment: None,
7104                        ..Default::default()
7105                    },
7106                ]);
7107                decl.collections = Some(vec![
7108                    fdecl::Collection{
7109                        name: Some("modular".to_string()),
7110                        durability: Some(fdecl::Durability::Transient),
7111                        environment: None,
7112                        allowed_offers: Some(fdecl::AllowedOffers::StaticOnly),
7113                        allow_long_names: None,
7114                        ..Default::default()
7115                    },
7116                ]);
7117                decl
7118            },
7119            result = Err(ErrorList::new(vec![
7120                // Duplicate services are allowed, for aggregation.
7121                Error::duplicate_field(DeclType::OfferProtocol, "target_name", "fuchsia.logger.LegacyLog"),
7122                Error::duplicate_field(DeclType::OfferDirectory, "target_name", "assets"),
7123                Error::duplicate_field(DeclType::OfferStorage, "target_name", "data"),
7124                Error::duplicate_field(DeclType::OfferRunner, "target_name", "duplicated"),
7125                Error::duplicate_field(DeclType::OfferResolver, "target_name", "duplicated"),
7126                Error::duplicate_field(DeclType::OfferEventStream, "target_name", "started"),
7127                Error::duplicate_field(DeclType::OfferDictionary, "target_name", "dict"),
7128            ])),
7129        },
7130        test_validate_offers_target_invalid => {
7131            input = {
7132                let mut decl = new_component_decl();
7133                decl.offers = Some(vec![
7134                    fdecl::Offer::Service(fdecl::OfferService {
7135                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
7136                        source_name: Some("logger".to_string()),
7137                        target: Some(fdecl::Ref::Child(
7138                        fdecl::ChildRef {
7139                            name: "netstack".to_string(),
7140                            collection: None,
7141                        }
7142                        )),
7143                        target_name: Some("fuchsia.logger.Log".to_string()),
7144                        ..Default::default()
7145                    }),
7146                    fdecl::Offer::Service(fdecl::OfferService {
7147                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
7148                        source_name: Some("logger".to_string()),
7149                        target: Some(fdecl::Ref::Collection(
7150                        fdecl::CollectionRef { name: "modular".to_string(), }
7151                        )),
7152                        target_name: Some("fuchsia.logger.Log".to_string()),
7153                        ..Default::default()
7154                    }),
7155                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
7156                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
7157                        source_name: Some("legacy_logger".to_string()),
7158                        target: Some(fdecl::Ref::Child(
7159                        fdecl::ChildRef {
7160                            name: "netstack".to_string(),
7161                            collection: None,
7162                        }
7163                        )),
7164                        target_name: Some("fuchsia.logger.LegacyLog".to_string()),
7165                        dependency_type: Some(fdecl::DependencyType::Weak),
7166                        ..Default::default()
7167                    }),
7168                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
7169                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
7170                        source_name: Some("legacy_logger".to_string()),
7171                        target: Some(fdecl::Ref::Collection(
7172                        fdecl::CollectionRef { name: "modular".to_string(), }
7173                        )),
7174                        target_name: Some("fuchsia.logger.LegacyLog".to_string()),
7175                        dependency_type: Some(fdecl::DependencyType::Strong),
7176                        ..Default::default()
7177                    }),
7178                    fdecl::Offer::Directory(fdecl::OfferDirectory {
7179                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
7180                        source_name: Some("assets".to_string()),
7181                        target: Some(fdecl::Ref::Child(
7182                        fdecl::ChildRef {
7183                            name: "netstack".to_string(),
7184                            collection: None,
7185                        }
7186                        )),
7187                        target_name: Some("data".to_string()),
7188                        rights: Some(fio::Operations::CONNECT),
7189                        subdir: None,
7190                        dependency_type: Some(fdecl::DependencyType::Strong),
7191                        ..Default::default()
7192                    }),
7193                    fdecl::Offer::Directory(fdecl::OfferDirectory {
7194                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
7195                        source_name: Some("assets".to_string()),
7196                        target: Some(fdecl::Ref::Collection(
7197                        fdecl::CollectionRef { name: "modular".to_string(), }
7198                        )),
7199                        target_name: Some("data".to_string()),
7200                        rights: Some(fio::Operations::CONNECT),
7201                        subdir: None,
7202                        dependency_type: Some(fdecl::DependencyType::Weak),
7203                        ..Default::default()
7204                    }),
7205                    fdecl::Offer::Storage(fdecl::OfferStorage {
7206                        source_name: Some("data".to_string()),
7207                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
7208                        target: Some(fdecl::Ref::Child(
7209                            fdecl::ChildRef {
7210                                name: "netstack".to_string(),
7211                                collection: None,
7212                            }
7213                        )),
7214                        target_name: Some("data".to_string()),
7215                        ..Default::default()
7216                    }),
7217                    fdecl::Offer::Storage(fdecl::OfferStorage {
7218                        source_name: Some("data".to_string()),
7219                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
7220                        target: Some(fdecl::Ref::Collection(
7221                            fdecl::CollectionRef { name: "modular".to_string(), }
7222                        )),
7223                        target_name: Some("data".to_string()),
7224                        ..Default::default()
7225                    }),
7226                    fdecl::Offer::Runner(fdecl::OfferRunner {
7227                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
7228                        source_name: Some("elf".to_string()),
7229                        target: Some(fdecl::Ref::Child(
7230                            fdecl::ChildRef {
7231                                name: "netstack".to_string(),
7232                                collection: None,
7233                            }
7234                        )),
7235                        target_name: Some("elf".to_string()),
7236                        ..Default::default()
7237                    }),
7238                    fdecl::Offer::Runner(fdecl::OfferRunner {
7239                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
7240                        source_name: Some("elf".to_string()),
7241                        target: Some(fdecl::Ref::Collection(
7242                        fdecl::CollectionRef { name: "modular".to_string(), }
7243                        )),
7244                        target_name: Some("elf".to_string()),
7245                        ..Default::default()
7246                    }),
7247                    fdecl::Offer::Resolver(fdecl::OfferResolver {
7248                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
7249                        source_name: Some("pkg".to_string()),
7250                        target: Some(fdecl::Ref::Child(
7251                            fdecl::ChildRef {
7252                                name: "netstack".to_string(),
7253                                collection: None,
7254                            }
7255                        )),
7256                        target_name: Some("pkg".to_string()),
7257                        ..Default::default()
7258                    }),
7259                    fdecl::Offer::Resolver(fdecl::OfferResolver {
7260                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
7261                        source_name: Some("pkg".to_string()),
7262                        target: Some(fdecl::Ref::Collection(
7263                        fdecl::CollectionRef { name: "modular".to_string(), }
7264                        )),
7265                        target_name: Some("pkg".to_string()),
7266                        ..Default::default()
7267                    }),
7268                    fdecl::Offer::Dictionary(fdecl::OfferDictionary {
7269                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
7270                        source_name: Some("pkg".to_string()),
7271                        target: Some(fdecl::Ref::Child(
7272                            fdecl::ChildRef {
7273                                name: "netstack".to_string(),
7274                                collection: None,
7275                            }
7276                        )),
7277                        target_name: Some("pkg".to_string()),
7278                        dependency_type: Some(fdecl::DependencyType::Strong),
7279                        ..Default::default()
7280                    }),
7281                    fdecl::Offer::Dictionary(fdecl::OfferDictionary {
7282                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
7283                        source_name: Some("pkg".to_string()),
7284                        target: Some(fdecl::Ref::Collection(
7285                        fdecl::CollectionRef { name: "modular".to_string(), }
7286                        )),
7287                        target_name: Some("pkg".to_string()),
7288                        dependency_type: Some(fdecl::DependencyType::Strong),
7289                        ..Default::default()
7290                    }),
7291                ]);
7292                decl
7293            },
7294            result = Err(ErrorList::new(vec![
7295                Error::invalid_child(DeclType::OfferService, "target", "netstack"),
7296                Error::invalid_collection(DeclType::OfferService, "target", "modular"),
7297                Error::invalid_child(DeclType::OfferProtocol, "target", "netstack"),
7298                Error::invalid_collection(DeclType::OfferProtocol, "target", "modular"),
7299                Error::invalid_child(DeclType::OfferDirectory, "target", "netstack"),
7300                Error::invalid_collection(DeclType::OfferDirectory, "target", "modular"),
7301                Error::invalid_child(DeclType::OfferStorage, "target", "netstack"),
7302                Error::invalid_collection(DeclType::OfferStorage, "target", "modular"),
7303                Error::invalid_child(DeclType::OfferRunner, "target", "netstack"),
7304                Error::invalid_collection(DeclType::OfferRunner, "target", "modular"),
7305                Error::invalid_child(DeclType::OfferResolver, "target", "netstack"),
7306                Error::invalid_collection(DeclType::OfferResolver, "target", "modular"),
7307                Error::invalid_child(DeclType::OfferDictionary, "target", "netstack"),
7308                Error::invalid_collection(DeclType::OfferDictionary, "target", "modular"),
7309            ])),
7310        },
7311        test_validate_offers_target_dictionary => {
7312            input = fdecl::Component {
7313                offers: Some(vec![
7314                    // Offer to static dictionary is ok
7315                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
7316                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
7317                        source_name: Some("p".to_string()),
7318                        target: Some(fdecl::Ref::Capability(
7319                            fdecl::CapabilityRef {
7320                                name: "dict".into(),
7321                            },
7322                        )),
7323                        target_name: Some("p".into()),
7324                        dependency_type: Some(fdecl::DependencyType::Strong),
7325                        ..Default::default()
7326                    }),
7327                    // Offer to dynamic dictionary is forbidden
7328                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
7329                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
7330                        source_name: Some("p".to_string()),
7331                        target: Some(fdecl::Ref::Capability(
7332                            fdecl::CapabilityRef {
7333                                name: "dynamic".into(),
7334                            },
7335                        )),
7336                        target_name: Some("p".into()),
7337                        dependency_type: Some(fdecl::DependencyType::Strong),
7338                        ..Default::default()
7339                    }),
7340                ]),
7341                capabilities: Some(vec![
7342                    fdecl::Capability::Dictionary(fdecl::Dictionary {
7343                        name: Some("dict".into()),
7344                        ..Default::default()
7345                    }),
7346                    fdecl::Capability::Dictionary(fdecl::Dictionary {
7347                        name: Some("dynamic".into()),
7348                        source_path: Some("/out/dir".into()),
7349                        ..Default::default()
7350                    }),
7351                ]),
7352                ..Default::default()
7353            },
7354            result = Err(ErrorList::new(vec![
7355                Error::invalid_field(DeclType::OfferProtocol, "target"),
7356            ])),
7357        },
7358        test_validate_offers_invalid_source_collection => {
7359            input = {
7360                let mut decl = new_component_decl();
7361                decl.collections = Some(vec![
7362                    fdecl::Collection {
7363                        name: Some("col".to_string()),
7364                        durability: Some(fdecl::Durability::Transient),
7365                        allowed_offers: Some(fdecl::AllowedOffers::StaticOnly),
7366                        allow_long_names: None,
7367                        ..Default::default()
7368                    }
7369                ]);
7370                decl.children = Some(vec![
7371                    fdecl::Child {
7372                        name: Some("child".to_string()),
7373                        url: Some("fuchsia-pkg://fuchsia.com/foo".to_string()),
7374                        startup: Some(fdecl::StartupMode::Lazy),
7375                        on_terminate: None,
7376                        ..Default::default()
7377                    }
7378                ]);
7379                decl.offers = Some(vec![
7380                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
7381                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef { name: "col".to_string() })),
7382                        source_name: Some("a".to_string()),
7383                        target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child".to_string(), collection: None })),
7384                        target_name: Some("a".to_string()),
7385                        dependency_type: Some(fdecl::DependencyType::Strong),
7386                        ..Default::default()
7387                    }),
7388                    fdecl::Offer::Directory(fdecl::OfferDirectory {
7389                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef { name: "col".to_string() })),
7390                        source_name: Some("b".to_string()),
7391                        target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child".to_string(), collection: None })),
7392                        target_name: Some("b".to_string()),
7393                        rights: Some(fio::Operations::CONNECT),
7394                        subdir: None,
7395                        dependency_type: Some(fdecl::DependencyType::Strong),
7396                        ..Default::default()
7397                    }),
7398                    fdecl::Offer::Storage(fdecl::OfferStorage {
7399                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef { name: "col".to_string() })),
7400                        source_name: Some("c".to_string()),
7401                        target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child".to_string(), collection: None })),
7402                        target_name: Some("c".to_string()),
7403                        ..Default::default()
7404                    }),
7405                    fdecl::Offer::Runner(fdecl::OfferRunner {
7406                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef { name: "col".to_string() })),
7407                        source_name: Some("d".to_string()),
7408                        target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child".to_string(), collection: None })),
7409                        target_name: Some("d".to_string()),
7410                        ..Default::default()
7411                    }),
7412                    fdecl::Offer::Resolver(fdecl::OfferResolver {
7413                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef { name: "col".to_string() })),
7414                        source_name: Some("e".to_string()),
7415                        target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child".to_string(), collection: None })),
7416                        target_name: Some("e".to_string()),
7417                        ..Default::default()
7418                    }),
7419                    fdecl::Offer::Dictionary(fdecl::OfferDictionary {
7420                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef { name: "col".to_string() })),
7421                        source_name: Some("f".to_string()),
7422                        target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child".to_string(), collection: None })),
7423                        target_name: Some("f".to_string()),
7424                        dependency_type: Some(fdecl::DependencyType::Strong),
7425                        ..Default::default()
7426                    }),
7427                ]);
7428                decl
7429            },
7430            result = Err(ErrorList::new(vec![
7431                Error::invalid_field(DeclType::OfferProtocol, "source"),
7432                Error::invalid_field(DeclType::OfferDirectory, "source"),
7433                Error::invalid_field(DeclType::OfferStorage, "source"),
7434                Error::invalid_field(DeclType::OfferRunner, "source"),
7435                Error::invalid_field(DeclType::OfferResolver, "source"),
7436                Error::invalid_field(DeclType::OfferDictionary, "source"),
7437            ])),
7438        },
7439        test_validate_offers_source_collection => {
7440            input = {
7441                let mut decl = new_component_decl();
7442                decl.collections = Some(vec![
7443                    fdecl::Collection {
7444                        name: Some("col".to_string()),
7445                        durability: Some(fdecl::Durability::Transient),
7446                        allowed_offers: Some(fdecl::AllowedOffers::StaticOnly),
7447                        allow_long_names: None,
7448                        ..Default::default()
7449                    }
7450                ]);
7451                decl.children = Some(vec![
7452                    fdecl::Child {
7453                        name: Some("child".to_string()),
7454                        url: Some("fuchsia-pkg://fuchsia.com/foo".to_string()),
7455                        startup: Some(fdecl::StartupMode::Lazy),
7456                        on_terminate: None,
7457                        ..Default::default()
7458                    }
7459                ]);
7460                decl.offers = Some(vec![
7461                    fdecl::Offer::Service(fdecl::OfferService {
7462                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef { name: "col".to_string() })),
7463                        source_name: Some("a".to_string()),
7464                        target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child".to_string(), collection: None })),
7465                        target_name: Some("a".to_string()),
7466                        ..Default::default()
7467                    })
7468                ]);
7469                decl
7470            },
7471            result = Ok(()),
7472        },
7473        test_validate_offers_invalid_capability_from_self => {
7474            input = {
7475                let mut decl = new_component_decl();
7476                decl.children = Some(vec![
7477                    fdecl::Child {
7478                        name: Some("child".to_string()),
7479                        url: Some("fuchsia-pkg://fuchsia.com/foo".to_string()),
7480                        startup: Some(fdecl::StartupMode::Lazy),
7481                        ..Default::default()
7482                    }
7483                ]);
7484                decl.offers = Some(vec![
7485                    fdecl::Offer::Service(fdecl::OfferService {
7486                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
7487                        source_name: Some("fuchsia.some.library.SomeProtocol".into()),
7488                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
7489                            name: "child".into(),
7490                            collection: None
7491                        })),
7492                        target_name: Some("foo".into()),
7493                        ..Default::default()
7494                    }),
7495                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
7496                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
7497                        source_name: Some("fuchsia.some.library.SomeProtocol".into()),
7498                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
7499                            name: "child".into(),
7500                            collection: None
7501                        })),
7502                        target_name: Some("bar".into()),
7503                        dependency_type: Some(fdecl::DependencyType::Strong),
7504                        ..Default::default()
7505                    }),
7506                    fdecl::Offer::Directory(fdecl::OfferDirectory {
7507                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
7508                        source_name: Some("dir".into()),
7509                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
7510                            name: "child".into(),
7511                            collection: None
7512                        })),
7513                        target_name: Some("assets".into()),
7514                        dependency_type: Some(fdecl::DependencyType::Strong),
7515                        ..Default::default()
7516                    }),
7517                    fdecl::Offer::Runner(fdecl::OfferRunner {
7518                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
7519                        source_name: Some("source_elf".into()),
7520                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
7521                            name: "child".into(),
7522                            collection: None
7523                        })),
7524                        target_name: Some("elf".into()),
7525                        ..Default::default()
7526                    }),
7527                    fdecl::Offer::Resolver(fdecl::OfferResolver {
7528                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
7529                        source_name: Some("source_pkg".into()),
7530                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
7531                            name: "child".into(),
7532                            collection: None
7533                        })),
7534                        target_name: Some("pkg".into()),
7535                        ..Default::default()
7536                    }),
7537                    fdecl::Offer::Dictionary(fdecl::OfferDictionary {
7538                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
7539                        source_name: Some("source_dict".into()),
7540                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
7541                            name: "child".into(),
7542                            collection: None
7543                        })),
7544                        target_name: Some("dict".into()),
7545                        dependency_type: Some(fdecl::DependencyType::Strong),
7546                        ..Default::default()
7547                    }),
7548                    fdecl::Offer::Storage(fdecl::OfferStorage {
7549                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
7550                        source_name: Some("source_storage".into()),
7551                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
7552                            name: "child".into(),
7553                            collection: None
7554                        })),
7555                        target_name: Some("storage".into()),
7556                        ..Default::default()
7557                    }),
7558                    fdecl::Offer::Config(fdecl::OfferConfiguration {
7559                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
7560                        source_name: Some("source_config".into()),
7561                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
7562                            name: "child".into(),
7563                            collection: None
7564                        })),
7565                        target_name: Some("config".into()),
7566                        ..Default::default()
7567                    }),
7568                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
7569                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
7570                        source_name: Some("fuchsia.some.library.SomeProtocol".into()),
7571                        source_dictionary: Some("dict/inner".into()),
7572                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
7573                            name: "child".into(),
7574                            collection: None
7575                        })),
7576                        target_name: Some("baz".into()),
7577                        dependency_type: Some(fdecl::DependencyType::Strong),
7578                        ..Default::default()
7579                    }),
7580                ]);
7581                decl
7582            },
7583            result = Err(ErrorList::new(vec![
7584                Error::invalid_capability(
7585                    DeclType::OfferService,
7586                    "source",
7587                    "fuchsia.some.library.SomeProtocol"),
7588                Error::invalid_capability(
7589                    DeclType::OfferProtocol,
7590                    "source",
7591                    "fuchsia.some.library.SomeProtocol"),
7592                Error::invalid_capability(DeclType::OfferDirectory, "source", "dir"),
7593                Error::invalid_capability(DeclType::OfferRunner, "source", "source_elf"),
7594                Error::invalid_capability(DeclType::OfferResolver, "source", "source_pkg"),
7595                Error::invalid_capability(DeclType::OfferDictionary, "source", "source_dict"),
7596                Error::invalid_capability(DeclType::OfferStorage, "source", "source_storage"),
7597                Error::invalid_capability(DeclType::OfferConfig, "source", "source_config"),
7598                Error::invalid_capability(DeclType::OfferProtocol, "source", "dict"),
7599            ])),
7600        },
7601        test_validate_offers_long_dependency_cycle => {
7602            input = {
7603                let mut decl = new_component_decl();
7604                let dependencies = vec![
7605                    ("d", "b"),
7606                    ("a", "b"),
7607                    ("b", "c"),
7608                    ("b", "d"),
7609                    ("c", "a"),
7610                ];
7611                let offers = dependencies.into_iter().map(|(from,to)|
7612                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
7613                        source: Some(fdecl::Ref::Child(
7614                        fdecl::ChildRef { name: from.to_string(), collection: None },
7615                        )),
7616                        source_name: Some(format!("thing_{}", from)),
7617                        target: Some(fdecl::Ref::Child(
7618                        fdecl::ChildRef { name: to.to_string(), collection: None },
7619                        )),
7620                        target_name: Some(format!("thing_{}", from)),
7621                        dependency_type: Some(fdecl::DependencyType::Strong),
7622                        ..Default::default()
7623                    })).collect();
7624                let children = ["a", "b", "c", "d"].iter().map(|name| {
7625                    fdecl::Child {
7626                        name: Some(name.to_string()),
7627                        url: Some(format!("fuchsia-pkg://fuchsia.com/pkg#meta/{}.cm", name)),
7628                        startup: Some(fdecl::StartupMode::Lazy),
7629                        on_terminate: None,
7630                        environment: None,
7631                        ..Default::default()
7632                    }
7633                }).collect();
7634                decl.offers = Some(offers);
7635                decl.children = Some(children);
7636                decl
7637            },
7638            result = Err(ErrorList::new(vec![
7639                Error::dependency_cycle("{{child a -> child b -> child c -> child a}, {child b -> child d -> child b}}")
7640            ])),
7641        },
7642        test_validate_offers_not_required_invalid_source_service => {
7643            input = {
7644                let mut decl = generate_offer_different_source_and_availability_decl(
7645                    |source, availability, target_name|
7646                        fdecl::Offer::Service(fdecl::OfferService {
7647                            source: Some(source),
7648                            source_name: Some("fuchsia.examples.Echo".to_string()),
7649                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
7650                                name: "sink".to_string(),
7651                                collection: None,
7652                            })),
7653                            target_name: Some(target_name.into()),
7654                            availability: Some(availability),
7655                            ..Default::default()
7656                        })
7657                );
7658                decl.capabilities = Some(vec![
7659                    fdecl::Capability::Service(fdecl::Service {
7660                        name: Some("fuchsia.examples.Echo".to_string()),
7661                        source_path: Some("/svc/fuchsia.examples.Echo".to_string()),
7662                        ..Default::default()
7663                    }),
7664                ]);
7665                decl
7666            },
7667            result = {
7668                Err(ErrorList::new(vec![
7669                    Error::availability_must_be_optional(
7670                        DeclType::OfferService,
7671                        "availability",
7672                        Some(&"fuchsia.examples.Echo".to_string()),
7673                    ),
7674                    Error::availability_must_be_optional(
7675                        DeclType::OfferService,
7676                        "availability",
7677                        Some(&"fuchsia.examples.Echo".to_string()),
7678                    ),
7679                ]))
7680            },
7681        },
7682        test_validate_offers_not_required_invalid_source_protocol => {
7683            input = {
7684                let mut decl = generate_offer_different_source_and_availability_decl(
7685                    |source, availability, target_name|
7686                        fdecl::Offer::Protocol(fdecl::OfferProtocol {
7687                            source: Some(source),
7688                            source_name: Some("fuchsia.examples.Echo".to_string()),
7689                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
7690                                name: "sink".to_string(),
7691                                collection: None,
7692                            })),
7693                            target_name: Some(target_name.into()),
7694                            dependency_type: Some(fdecl::DependencyType::Strong),
7695                            availability: Some(availability),
7696                            ..Default::default()
7697                        })
7698                );
7699                decl.capabilities = Some(vec![
7700                    fdecl::Capability::Protocol(fdecl::Protocol {
7701                        name: Some("fuchsia.examples.Echo".to_string()),
7702                        source_path: Some("/svc/fuchsia.examples.Echo".to_string()),
7703                        ..Default::default()
7704                    }),
7705                ]);
7706                decl
7707            },
7708            result = {
7709                Err(ErrorList::new(vec![
7710                    Error::availability_must_be_optional(
7711                        DeclType::OfferProtocol,
7712                        "availability",
7713                        Some(&"fuchsia.examples.Echo".to_string()),
7714                    ),
7715                    Error::availability_must_be_optional(
7716                        DeclType::OfferProtocol,
7717                        "availability",
7718                        Some(&"fuchsia.examples.Echo".to_string()),
7719                    ),
7720                ]))
7721            },
7722        },
7723        test_validate_offers_not_required_invalid_source_directory => {
7724            input = {
7725                let mut decl = generate_offer_different_source_and_availability_decl(
7726                    |source, availability, target_name|
7727                        fdecl::Offer::Directory(fdecl::OfferDirectory {
7728                            source: Some(source),
7729                            source_name: Some("assets".to_string()),
7730                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
7731                                name: "sink".to_string(),
7732                                collection: None,
7733                            })),
7734                            target_name: Some(target_name.into()),
7735                            rights: Some(fio::Operations::CONNECT),
7736                            subdir: None,
7737                            dependency_type: Some(fdecl::DependencyType::Weak),
7738                            availability: Some(availability),
7739                            ..Default::default()
7740                        })
7741                );
7742                decl.capabilities = Some(vec![
7743                    fdecl::Capability::Directory(fdecl::Directory {
7744                        name: Some("assets".to_string()),
7745                        source_path: Some("/assets".to_string()),
7746                        rights: Some(fio::Operations::CONNECT),
7747                        ..Default::default()
7748                    }),
7749                ]);
7750                decl
7751            },
7752            result = {
7753                Err(ErrorList::new(vec![
7754                    Error::availability_must_be_optional(
7755                        DeclType::OfferDirectory,
7756                        "availability",
7757                        Some(&"assets".to_string()),
7758                    ),
7759                    Error::availability_must_be_optional(
7760                        DeclType::OfferDirectory,
7761                        "availability",
7762                        Some(&"assets".to_string()),
7763                    ),
7764                ]))
7765            },
7766        },
7767        test_validate_offers_not_required_invalid_source_storage => {
7768            input = {
7769                let mut decl = new_component_decl();
7770                decl.children = Some(vec![
7771                    fdecl::Child {
7772                        name: Some("sink".to_string()),
7773                        url: Some("fuchsia-pkg://fuchsia.com/sink#meta/sink.cm".to_string()),
7774                        startup: Some(fdecl::StartupMode::Lazy),
7775                        on_terminate: None,
7776                        environment: None,
7777                        ..Default::default()
7778                    },
7779                ]);
7780                decl.capabilities = Some(vec![
7781                    fdecl::Capability::Storage(fdecl::Storage {
7782                        name: Some("data".to_string()),
7783                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
7784                        backing_dir: Some("minfs".to_string()),
7785                        subdir: None,
7786                        storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker),
7787                        ..Default::default()
7788                    }),
7789                ]);
7790                let new_offer = |source: fdecl::Ref, availability: fdecl::Availability,
7791                                        target_name: &str|
7792                {
7793                    fdecl::Offer::Storage(fdecl::OfferStorage {
7794                        source: Some(source),
7795                        source_name: Some("data".to_string()),
7796                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
7797                            name: "sink".to_string(),
7798                            collection: None,
7799                        })),
7800                        target_name: Some(target_name.into()),
7801                        availability: Some(availability),
7802                        ..Default::default()
7803                    })
7804                };
7805                decl.offers = Some(vec![
7806                    // These offers are fine, offers with a source of parent or void can be
7807                    // optional.
7808                    new_offer(
7809                        fdecl::Ref::Parent(fdecl::ParentRef {}),
7810                        fdecl::Availability::Required,
7811                        "data0",
7812                    ),
7813                    new_offer(
7814                        fdecl::Ref::Parent(fdecl::ParentRef {}),
7815                        fdecl::Availability::Optional,
7816                        "data1",
7817                    ),
7818                    new_offer(
7819                        fdecl::Ref::Parent(fdecl::ParentRef {}),
7820                        fdecl::Availability::SameAsTarget,
7821                        "data2",
7822                    ),
7823                    new_offer(
7824                        fdecl::Ref::VoidType(fdecl::VoidRef {}),
7825                        fdecl::Availability::Optional,
7826                        "data3",
7827                    ),
7828                    // These offers are not fine, offers with a source other than parent or void
7829                    // must be required.
7830                    new_offer(
7831                        fdecl::Ref::Self_(fdecl::SelfRef {}),
7832                        fdecl::Availability::Optional,
7833                        "data4",
7834                    ),
7835                    new_offer(
7836                        fdecl::Ref::Self_(fdecl::SelfRef {}),
7837                        fdecl::Availability::SameAsTarget,
7838                        "data5",
7839                    ),
7840                    // These offers are also not fine, offers with a source of void must be optional
7841                    new_offer(
7842                        fdecl::Ref::VoidType(fdecl::VoidRef {}),
7843                        fdecl::Availability::Required,
7844                        "data6",
7845                    ),
7846                    new_offer(
7847                        fdecl::Ref::VoidType(fdecl::VoidRef {}),
7848                        fdecl::Availability::SameAsTarget,
7849                        "data7",
7850                    ),
7851                ]);
7852                decl
7853            },
7854            result = {
7855                Err(ErrorList::new(vec![
7856                    Error::availability_must_be_optional(
7857                        DeclType::OfferStorage,
7858                        "availability",
7859                        Some(&"data".to_string()),
7860                    ),
7861                    Error::availability_must_be_optional(
7862                        DeclType::OfferStorage,
7863                        "availability",
7864                        Some(&"data".to_string()),
7865                    ),
7866                ]))
7867            },
7868        },
7869
7870        test_validate_offers_valid_service_aggregation => {
7871            input = {
7872                let mut decl = new_component_decl();
7873                decl.offers = Some(vec![
7874                    fdecl::Offer::Service(fdecl::OfferService {
7875                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
7876                            name: "coll_a".to_string()
7877                        })),
7878                        source_name: Some("fuchsia.logger.Log".to_string()),
7879                        target: Some(fdecl::Ref::Child(
7880                            fdecl::ChildRef {
7881                                name: "child_c".to_string(),
7882                                collection: None,
7883                            }
7884                        )),
7885                        target_name: Some("fuchsia.logger.Log".to_string()),
7886                        source_instance_filter: None,
7887                        ..Default::default()
7888                    }),
7889                    fdecl::Offer::Service(fdecl::OfferService {
7890                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
7891                            name: "coll_b".to_string()
7892                        })),
7893                        source_name: Some("fuchsia.logger.Log".to_string()),
7894                        target: Some(fdecl::Ref::Child(
7895                            fdecl::ChildRef {
7896                                name: "child_c".to_string(),
7897                                collection: None,
7898                            }
7899                        )),
7900                        target_name: Some("fuchsia.logger.Log".to_string()),
7901                        source_instance_filter: Some(vec!["a_different_default".to_string()]),
7902                        ..Default::default()
7903                    })
7904                ]);
7905                decl.children = Some(vec![
7906                    fdecl::Child {
7907                        name: Some("child_c".to_string()),
7908                        url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
7909                        startup: Some(fdecl::StartupMode::Lazy),
7910                        ..Default::default()
7911                    },
7912                ]);
7913                decl.collections = Some(vec![
7914                    fdecl::Collection {
7915                        name: Some("coll_a".into()),
7916                        durability: Some(fdecl::Durability::Transient),
7917                        ..Default::default()
7918                    },
7919                    fdecl::Collection {
7920                        name: Some("coll_b".into()),
7921                        durability: Some(fdecl::Durability::Transient),
7922                        ..Default::default()
7923                    },
7924                ]);
7925                decl
7926            },
7927            result = Ok(()),
7928        },
7929
7930        // dictionaries
7931        test_validate_source_dictionary => {
7932            input = fdecl::Component {
7933                program: Some(fdecl::Program {
7934                    runner: Some("elf".into()),
7935                    info: Some(fdata::Dictionary {
7936                        entries: None,
7937                        ..Default::default()
7938                    }),
7939                    ..Default::default()
7940                }),
7941                uses: Some(vec![
7942                    fdecl::Use::Protocol(fdecl::UseProtocol {
7943                        dependency_type: Some(fdecl::DependencyType::Strong),
7944                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
7945                        source_dictionary: Some("bad//".into()),
7946                        source_name: Some("foo".into()),
7947                        target_path: Some("/svc/foo".into()),
7948                        ..Default::default()
7949                    }),
7950                ]),
7951                exposes: Some(vec![
7952                    fdecl::Expose::Directory(fdecl::ExposeDirectory {
7953                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
7954                            name: "missing".into(),
7955                            collection: None,
7956                        })),
7957                        source_dictionary: Some("in/dict".into()),
7958                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
7959                        source_name: Some("foo".into()),
7960                        target_name: Some("bar".into()),
7961                        ..Default::default()
7962                    }),
7963                ]),
7964                offers: Some(vec![
7965                    fdecl::Offer::Service(fdecl::OfferService {
7966                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
7967                        source_dictionary: Some("bad//".into()),
7968                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
7969                            name: "child".into(),
7970                            collection: None,
7971                        })),
7972                        source_name: Some("foo".into()),
7973                        target_name: Some("bar".into()),
7974                        ..Default::default()
7975                    }),
7976                ]),
7977                children: Some(vec![
7978                    fdecl::Child {
7979                        name: Some("child".into()),
7980                        url: Some("fuchsia-pkg://child".into()),
7981                        startup: Some(fdecl::StartupMode::Lazy),
7982                        ..Default::default()
7983                    },
7984                ]),
7985                ..Default::default()
7986            },
7987            result = Err(ErrorList::new(vec![
7988                Error::invalid_field(DeclType::UseProtocol, "source_dictionary"),
7989                Error::invalid_child(DeclType::ExposeDirectory, "source", "missing"),
7990                Error::invalid_field(DeclType::OfferService, "source_dictionary"),
7991            ])),
7992        },
7993        test_validate_dictionary_too_long => {
7994            input = fdecl::Component {
7995                program: Some(fdecl::Program {
7996                    runner: Some("elf".into()),
7997                    info: Some(fdata::Dictionary {
7998                        entries: None,
7999                        ..Default::default()
8000                    }),
8001                    ..Default::default()
8002                }),
8003                uses: Some(vec![
8004                    fdecl::Use::Protocol(fdecl::UseProtocol {
8005                        dependency_type: Some(fdecl::DependencyType::Strong),
8006                        source: Some(fdecl::Ref::Parent( fdecl::ParentRef {} )),
8007                        source_dictionary: Some("a".repeat(4096)),
8008                        source_name: Some("foo".into()),
8009                        target_path: Some("/svc/foo".into()),
8010                        ..Default::default()
8011                    }),
8012                ]),
8013                ..Default::default()
8014            },
8015            result = Err(ErrorList::new(vec![
8016                Error::field_too_long(DeclType::UseProtocol, "source_dictionary"),
8017            ])),
8018        },
8019
8020        // environments
8021        test_validate_environment_empty => {
8022            input = {
8023                let mut decl = new_component_decl();
8024                decl.environments = Some(vec![fdecl::Environment {
8025                    name: None,
8026                    extends: None,
8027                    runners: None,
8028                    resolvers: None,
8029                    stop_timeout_ms: None,
8030                    debug_capabilities: None,
8031                    ..Default::default()
8032                }]);
8033                decl
8034            },
8035            result = Err(ErrorList::new(vec![
8036                Error::missing_field(DeclType::Environment, "name"),
8037                Error::missing_field(DeclType::Environment, "extends"),
8038            ])),
8039        },
8040
8041        test_validate_environment_no_stop_timeout => {
8042            input = {
8043                let mut decl = new_component_decl();
8044                decl.environments = Some(vec![fdecl::Environment {
8045                    name: Some("env".to_string()),
8046                    extends: Some(fdecl::EnvironmentExtends::None),
8047                    runners: None,
8048                    resolvers: None,
8049                    stop_timeout_ms: None,
8050                    ..Default::default()
8051                }]);
8052                decl
8053            },
8054            result = Err(ErrorList::new(vec![Error::missing_field(DeclType::Environment, "stop_timeout_ms")])),
8055        },
8056
8057        test_validate_environment_extends_stop_timeout => {
8058            input = {  let mut decl = new_component_decl();
8059                decl.environments = Some(vec![fdecl::Environment {
8060                    name: Some("env".to_string()),
8061                    extends: Some(fdecl::EnvironmentExtends::Realm),
8062                    runners: None,
8063                    resolvers: None,
8064                    stop_timeout_ms: None,
8065                    ..Default::default()
8066                }]);
8067                decl
8068            },
8069            result = Ok(()),
8070        },
8071        test_validate_environment_long_identifiers => {
8072            input = {
8073                let mut decl = new_component_decl();
8074                decl.environments = Some(vec![fdecl::Environment {
8075                    name: Some("a".repeat(256)),
8076                    extends: Some(fdecl::EnvironmentExtends::None),
8077                    runners: Some(vec![
8078                        fdecl::RunnerRegistration {
8079                            source_name: Some("a".repeat(256)),
8080                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
8081                            target_name: Some("a".repeat(256)),
8082                            ..Default::default()
8083                        },
8084                    ]),
8085                    resolvers: Some(vec![
8086                        fdecl::ResolverRegistration {
8087                            resolver: Some("a".repeat(256)),
8088                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
8089                            scheme: Some("a".repeat(256)),
8090                            ..Default::default()
8091                        },
8092                    ]),
8093                    stop_timeout_ms: Some(1234),
8094                    ..Default::default()
8095                }]);
8096                decl
8097            },
8098            result = Err(ErrorList::new(vec![
8099                Error::field_too_long(DeclType::Environment, "name"),
8100                Error::field_too_long(DeclType::RunnerRegistration, "source_name"),
8101                Error::field_too_long(DeclType::RunnerRegistration, "target_name"),
8102                Error::field_too_long(DeclType::ResolverRegistration, "resolver"),
8103                Error::field_too_long(DeclType::ResolverRegistration, "scheme"),
8104            ])),
8105        },
8106        test_validate_environment_empty_runner_resolver_fields => {
8107            input = {
8108                let mut decl = new_component_decl();
8109                decl.environments = Some(vec![fdecl::Environment {
8110                    name: Some("a".to_string()),
8111                    extends: Some(fdecl::EnvironmentExtends::None),
8112                    runners: Some(vec![
8113                        fdecl::RunnerRegistration {
8114                            source_name: None,
8115                            source: None,
8116                            target_name: None,
8117                            ..Default::default()
8118                        },
8119                    ]),
8120                    resolvers: Some(vec![
8121                        fdecl::ResolverRegistration {
8122                            resolver: None,
8123                            source: None,
8124                            scheme: None,
8125                            ..Default::default()
8126                        },
8127                    ]),
8128                    stop_timeout_ms: Some(1234),
8129                    ..Default::default()
8130                }]);
8131                decl
8132            },
8133            result = Err(ErrorList::new(vec![
8134                Error::missing_field(DeclType::RunnerRegistration, "source_name"),
8135                Error::missing_field(DeclType::RunnerRegistration, "source"),
8136                Error::missing_field(DeclType::RunnerRegistration, "target_name"),
8137                Error::missing_field(DeclType::ResolverRegistration, "resolver"),
8138                Error::missing_field(DeclType::ResolverRegistration, "source"),
8139                Error::missing_field(DeclType::ResolverRegistration, "scheme"),
8140            ])),
8141        },
8142        test_validate_environment_invalid_fields => {
8143            input = {
8144                let mut decl = new_component_decl();
8145                decl.environments = Some(vec![fdecl::Environment {
8146                    name: Some("a".to_string()),
8147                    extends: Some(fdecl::EnvironmentExtends::None),
8148                    runners: Some(vec![
8149                        fdecl::RunnerRegistration {
8150                            source_name: Some("^a".to_string()),
8151                            source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef{})),
8152                            target_name: Some("%a".to_string()),
8153                            ..Default::default()
8154                        },
8155                    ]),
8156                    resolvers: Some(vec![
8157                        fdecl::ResolverRegistration {
8158                            resolver: Some("^a".to_string()),
8159                            source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef{})),
8160                            scheme: Some("9scheme".to_string()),
8161                            ..Default::default()
8162                        },
8163                    ]),
8164                    stop_timeout_ms: Some(1234),
8165                    ..Default::default()
8166                }]);
8167                decl
8168            },
8169            result = Err(ErrorList::new(vec![
8170                Error::invalid_field(DeclType::RunnerRegistration, "source_name"),
8171                Error::invalid_field(DeclType::RunnerRegistration, "source"),
8172                Error::invalid_field(DeclType::RunnerRegistration, "target_name"),
8173                Error::invalid_field(DeclType::ResolverRegistration, "resolver"),
8174                Error::invalid_field(DeclType::ResolverRegistration, "source"),
8175                Error::invalid_field(DeclType::ResolverRegistration, "scheme"),
8176            ])),
8177        },
8178        test_validate_environment_missing_runner => {
8179            input = {
8180                let mut decl = new_component_decl();
8181                decl.environments = Some(vec![fdecl::Environment {
8182                    name: Some("a".to_string()),
8183                    extends: Some(fdecl::EnvironmentExtends::None),
8184                    runners: Some(vec![
8185                        fdecl::RunnerRegistration {
8186                            source_name: Some("dart".to_string()),
8187                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
8188                            target_name: Some("dart".to_string()),
8189                            ..Default::default()
8190                        },
8191                    ]),
8192                    resolvers: None,
8193                    stop_timeout_ms: Some(1234),
8194                    ..Default::default()
8195                }]);
8196                decl
8197            },
8198            result = Err(ErrorList::new(vec![
8199                Error::invalid_runner(DeclType::RunnerRegistration, "source_name", "dart"),
8200            ])),
8201        },
8202        test_validate_environment_duplicate_registrations => {
8203            input = {
8204                let mut decl = new_component_decl();
8205                decl.environments = Some(vec![fdecl::Environment {
8206                    name: Some("a".to_string()),
8207                    extends: Some(fdecl::EnvironmentExtends::None),
8208                    runners: Some(vec![
8209                        fdecl::RunnerRegistration {
8210                            source_name: Some("dart".to_string()),
8211                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
8212                            target_name: Some("dart".to_string()),
8213                            ..Default::default()
8214                        },
8215                        fdecl::RunnerRegistration {
8216                            source_name: Some("other-dart".to_string()),
8217                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
8218                            target_name: Some("dart".to_string()),
8219                            ..Default::default()
8220                        },
8221                    ]),
8222                    resolvers: Some(vec![
8223                        fdecl::ResolverRegistration {
8224                            resolver: Some("pkg_resolver".to_string()),
8225                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
8226                            scheme: Some("fuchsia-pkg".to_string()),
8227                            ..Default::default()
8228                        },
8229                        fdecl::ResolverRegistration {
8230                            resolver: Some("base_resolver".to_string()),
8231                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
8232                            scheme: Some("fuchsia-pkg".to_string()),
8233                            ..Default::default()
8234                        },
8235                    ]),
8236                    stop_timeout_ms: Some(1234),
8237                    ..Default::default()
8238                }]);
8239                decl
8240            },
8241            result = Err(ErrorList::new(vec![
8242                Error::duplicate_field(DeclType::RunnerRegistration, "target_name", "dart"),
8243                Error::duplicate_field(DeclType::ResolverRegistration, "scheme", "fuchsia-pkg"),
8244            ])),
8245        },
8246        test_validate_environment_from_missing_child => {
8247            input = {
8248                let mut decl = new_component_decl();
8249                decl.environments = Some(vec![fdecl::Environment {
8250                    name: Some("a".to_string()),
8251                    extends: Some(fdecl::EnvironmentExtends::None),
8252                    runners: Some(vec![
8253                        fdecl::RunnerRegistration {
8254                            source_name: Some("elf".to_string()),
8255                            source: Some(fdecl::Ref::Child(fdecl::ChildRef{
8256                                name: "missing".to_string(),
8257                                collection: None,
8258                            })),
8259                            target_name: Some("elf".to_string()),
8260                            ..Default::default()
8261                        },
8262                    ]),
8263                    resolvers: Some(vec![
8264                        fdecl::ResolverRegistration {
8265                            resolver: Some("pkg_resolver".to_string()),
8266                            source: Some(fdecl::Ref::Child(fdecl::ChildRef{
8267                                name: "missing".to_string(),
8268                                collection: None,
8269                            })),
8270                            scheme: Some("fuchsia-pkg".to_string()),
8271                            ..Default::default()
8272                        },
8273                    ]),
8274                    stop_timeout_ms: Some(1234),
8275                    ..Default::default()
8276                }]);
8277                decl
8278            },
8279            result = Err(ErrorList::new(vec![
8280                Error::invalid_child(DeclType::RunnerRegistration, "source", "missing"),
8281                Error::invalid_child(DeclType::ResolverRegistration, "source", "missing"),
8282            ])),
8283        },
8284        test_validate_environment_runner_child_cycle => {
8285            input = {
8286                let mut decl = new_component_decl();
8287                decl.environments = Some(vec![fdecl::Environment {
8288                    name: Some("env".to_string()),
8289                    extends: Some(fdecl::EnvironmentExtends::None),
8290                    runners: Some(vec![
8291                        fdecl::RunnerRegistration {
8292                            source_name: Some("elf".to_string()),
8293                            source: Some(fdecl::Ref::Child(fdecl::ChildRef{
8294                                name: "child".to_string(),
8295                                collection: None,
8296                            })),
8297                            target_name: Some("elf".to_string()),
8298                            ..Default::default()
8299                        },
8300                    ]),
8301                    resolvers: None,
8302                    stop_timeout_ms: Some(1234),
8303                    ..Default::default()
8304                }]);
8305                decl.children = Some(vec![fdecl::Child {
8306                    name: Some("child".to_string()),
8307                    startup: Some(fdecl::StartupMode::Lazy),
8308                    on_terminate: None,
8309                    url: Some("fuchsia-pkg://child".to_string()),
8310                    environment: Some("env".to_string()),
8311                    ..Default::default()
8312                }]);
8313                decl
8314            },
8315            result = Err(ErrorList::new(vec![
8316                Error::dependency_cycle(
8317                    "{{child child -> environment env -> child child}}"
8318                ),
8319            ])),
8320        },
8321        test_validate_environment_resolver_child_cycle => {
8322            input = {
8323                let mut decl = new_component_decl();
8324                decl.environments = Some(vec![fdecl::Environment {
8325                    name: Some("env".to_string()),
8326                    extends: Some(fdecl::EnvironmentExtends::None),
8327                    runners: None,
8328                    resolvers: Some(vec![
8329                        fdecl::ResolverRegistration {
8330                            resolver: Some("pkg_resolver".to_string()),
8331                            source: Some(fdecl::Ref::Child(fdecl::ChildRef{
8332                                name: "child".to_string(),
8333                                collection: None,
8334                            })),
8335                            scheme: Some("fuchsia-pkg".to_string()),
8336                            ..Default::default()
8337                        },
8338                    ]),
8339                    stop_timeout_ms: Some(1234),
8340                    ..Default::default()
8341                }]);
8342                decl.children = Some(vec![fdecl::Child {
8343                    name: Some("child".to_string()),
8344                    startup: Some(fdecl::StartupMode::Lazy),
8345                    on_terminate: None,
8346                    url: Some("fuchsia-pkg://child".to_string()),
8347                    environment: Some("env".to_string()),
8348                    ..Default::default()
8349                }]);
8350                decl
8351            },
8352            result = Err(ErrorList::new(vec![
8353                Error::dependency_cycle(
8354                    "{{child child -> environment env -> child child}}"
8355                ),
8356            ])),
8357        },
8358        test_validate_environment_resolver_multiple_children_cycle => {
8359            input = {
8360                let mut decl = new_component_decl();
8361                decl.environments = Some(vec![fdecl::Environment {
8362                    name: Some("env".to_string()),
8363                    extends: Some(fdecl::EnvironmentExtends::None),
8364                    runners: None,
8365                    resolvers: Some(vec![
8366                        fdecl::ResolverRegistration {
8367                            resolver: Some("pkg_resolver".to_string()),
8368                            source: Some(fdecl::Ref::Child(fdecl::ChildRef{
8369                                name: "a".to_string(),
8370                                collection: None,
8371                            })),
8372                            scheme: Some("fuchsia-pkg".to_string()),
8373                            ..Default::default()
8374                        },
8375                    ]),
8376                    stop_timeout_ms: Some(1234),
8377                    ..Default::default()
8378                }]);
8379                decl.children = Some(vec![
8380                    fdecl::Child {
8381                        name: Some("a".to_string()),
8382                        startup: Some(fdecl::StartupMode::Lazy),
8383                        on_terminate: None,
8384                        url: Some("fuchsia-pkg://child-a".to_string()),
8385                        environment: None,
8386                        ..Default::default()
8387                    },
8388                    fdecl::Child {
8389                        name: Some("b".to_string()),
8390                        startup: Some(fdecl::StartupMode::Lazy),
8391                        on_terminate: None,
8392                        url: Some("fuchsia-pkg://child-b".to_string()),
8393                        environment: Some("env".to_string()),
8394                        ..Default::default()
8395                    },
8396                ]);
8397                decl.offers = Some(vec![fdecl::Offer::Service(fdecl::OfferService {
8398                    source: Some(fdecl::Ref::Child(fdecl::ChildRef {
8399                        name: "b".to_string(),
8400                        collection: None,
8401                    })),
8402                    source_name: Some("thing".to_string()),
8403                    target: Some(fdecl::Ref::Child(
8404                    fdecl::ChildRef {
8405                        name: "a".to_string(),
8406                        collection: None,
8407                    }
8408                    )),
8409                    target_name: Some("thing".to_string()),
8410                    ..Default::default()
8411                })]);
8412                decl
8413            },
8414            result = Err(ErrorList::new(vec![
8415                Error::dependency_cycle(
8416                    "{{child a -> environment env -> child b -> child a}}"
8417                ),
8418            ])),
8419        },
8420        test_validate_environment_debug_empty => {
8421            input = {
8422                let mut decl = new_component_decl();
8423                decl.environments = Some(vec![
8424                    fdecl::Environment {
8425                        name: Some("a".to_string()),
8426                        extends: Some(fdecl::EnvironmentExtends::None),
8427                        stop_timeout_ms: Some(2),
8428                        debug_capabilities:Some(vec![
8429                            fdecl::DebugRegistration::Protocol(fdecl::DebugProtocolRegistration {
8430                                source: None,
8431                                source_name: None,
8432                                target_name: None,
8433                                ..Default::default()
8434                            }),
8435                    ]),
8436                    ..Default::default()
8437                }]);
8438                decl
8439            },
8440            result = Err(ErrorList::new(vec![
8441                Error::missing_field(DeclType::DebugProtocolRegistration, "source"),
8442                Error::missing_field(DeclType::DebugProtocolRegistration, "source_name"),
8443                Error::missing_field(DeclType::DebugProtocolRegistration, "target_name"),
8444            ])),
8445        },
8446        test_validate_environment_debug_log_identifier => {
8447            input = {
8448                let mut decl = new_component_decl();
8449                decl.environments = Some(vec![
8450                    fdecl::Environment {
8451                        name: Some("a".to_string()),
8452                        extends: Some(fdecl::EnvironmentExtends::None),
8453                        stop_timeout_ms: Some(2),
8454                        debug_capabilities:Some(vec![
8455                            fdecl::DebugRegistration::Protocol(fdecl::DebugProtocolRegistration {
8456                                source: Some(fdecl::Ref::Child(fdecl::ChildRef {
8457                                    name: "a".repeat(256),
8458                                    collection: None,
8459                                })),
8460                                source_name: Some(format!("{}", "a".repeat(256))),
8461                                target_name: Some(format!("{}", "b".repeat(256))),
8462                                ..Default::default()
8463                            }),
8464                            fdecl::DebugRegistration::Protocol(fdecl::DebugProtocolRegistration {
8465                                source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
8466                                source_name: Some("a".to_string()),
8467                                target_name: Some(format!("{}", "b".repeat(256))),
8468                                ..Default::default()
8469                            }),
8470                    ]),
8471                    ..Default::default()
8472                }]);
8473                decl
8474            },
8475            result = Err(ErrorList::new(vec![
8476                Error::field_too_long(DeclType::DebugProtocolRegistration, "source.child.name"),
8477                Error::field_too_long(DeclType::DebugProtocolRegistration, "source_name"),
8478                Error::field_too_long(DeclType::DebugProtocolRegistration, "target_name"),
8479                Error::field_too_long(DeclType::DebugProtocolRegistration, "target_name"),
8480            ])),
8481        },
8482        test_validate_environment_debug_log_extraneous => {
8483            input = {
8484                let mut decl = new_component_decl();
8485                decl.environments = Some(vec![
8486                    fdecl::Environment {
8487                        name: Some("a".to_string()),
8488                        extends: Some(fdecl::EnvironmentExtends::None),
8489                        stop_timeout_ms: Some(2),
8490                        debug_capabilities:Some(vec![
8491                            fdecl::DebugRegistration::Protocol(fdecl::DebugProtocolRegistration {
8492                                source: Some(fdecl::Ref::Child(fdecl::ChildRef {
8493                                    name: "logger".to_string(),
8494                                    collection: Some("modular".to_string()),
8495                                })),
8496                                source_name: Some("fuchsia.logger.Log".to_string()),
8497                                target_name: Some("fuchsia.logger.Log".to_string()),
8498                                ..Default::default()
8499                            }),
8500                    ]),
8501                    ..Default::default()
8502                }]);
8503                decl
8504            },
8505            result = Err(ErrorList::new(vec![
8506                Error::extraneous_field(DeclType::DebugProtocolRegistration, "source.child.collection"),
8507            ])),
8508        },
8509        test_validate_environment_debug_log_invalid_identifiers => {
8510            input = {
8511                let mut decl = new_component_decl();
8512                decl.environments = Some(vec![
8513                    fdecl::Environment {
8514                        name: Some("a".to_string()),
8515                        extends: Some(fdecl::EnvironmentExtends::None),
8516                        stop_timeout_ms: Some(2),
8517                        debug_capabilities:Some(vec![
8518                            fdecl::DebugRegistration::Protocol(fdecl::DebugProtocolRegistration {
8519                                source: Some(fdecl::Ref::Child(fdecl::ChildRef {
8520                                    name: "^bad".to_string(),
8521                                    collection: None,
8522                                })),
8523                                source_name: Some("foo/".to_string()),
8524                                target_name: Some("/".to_string()),
8525                                ..Default::default()
8526                            }),
8527                    ]),
8528                    ..Default::default()
8529                }]);
8530                decl
8531            },
8532            result = Err(ErrorList::new(vec![
8533                Error::invalid_field(DeclType::DebugProtocolRegistration, "source.child.name"),
8534                Error::invalid_field(DeclType::DebugProtocolRegistration, "source_name"),
8535                Error::invalid_field(DeclType::DebugProtocolRegistration, "target_name"),
8536            ])),
8537        },
8538        test_validate_environment_debug_log_invalid_child => {
8539            input = {
8540                let mut decl = new_component_decl();
8541                decl.environments = Some(vec![
8542                    fdecl::Environment {
8543                        name: Some("a".to_string()),
8544                        extends: Some(fdecl::EnvironmentExtends::None),
8545                        stop_timeout_ms: Some(2),
8546                        debug_capabilities:Some(vec![
8547                            fdecl::DebugRegistration::Protocol(fdecl::DebugProtocolRegistration {
8548                                source: Some(fdecl::Ref::Child(fdecl::ChildRef {
8549                                    name: "logger".to_string(),
8550                                    collection: None,
8551                                })),
8552                                source_name: Some("fuchsia.logger.LegacyLog".to_string()),
8553                                target_name: Some("fuchsia.logger.LegacyLog".to_string()),
8554                                ..Default::default()
8555                            }),
8556                    ]),
8557                    ..Default::default()
8558                }]);
8559                decl.children = Some(vec![
8560                    fdecl::Child {
8561                        name: Some("netstack".to_string()),
8562                        url: Some("fuchsia-pkg://fuchsia.com/netstack/stable#meta/netstack.cm".to_string()),
8563                        startup: Some(fdecl::StartupMode::Lazy),
8564                        on_terminate: None,
8565                        environment: None,
8566                        ..Default::default()
8567                    },
8568                ]);
8569                decl
8570            },
8571            result = Err(ErrorList::new(vec![
8572                Error::invalid_child(DeclType::DebugProtocolRegistration, "source", "logger"),
8573
8574            ])),
8575        },
8576        test_validate_environment_debug_source_capability => {
8577            input = {
8578                let mut decl = new_component_decl();
8579                decl.environments = Some(vec![
8580                    fdecl::Environment {
8581                        name: Some("a".to_string()),
8582                        extends: Some(fdecl::EnvironmentExtends::None),
8583                        stop_timeout_ms: Some(2),
8584                        debug_capabilities:Some(vec![
8585                            fdecl::DebugRegistration::Protocol(fdecl::DebugProtocolRegistration {
8586                                source: Some(fdecl::Ref::Capability(fdecl::CapabilityRef {
8587                                    name: "storage".to_string(),
8588                                })),
8589                                source_name: Some("fuchsia.sys2.StorageAdmin".to_string()),
8590                                target_name: Some("fuchsia.sys2.StorageAdmin".to_string()),
8591                                ..Default::default()
8592                            }),
8593                    ]),
8594                    ..Default::default()
8595                }]);
8596                decl
8597            },
8598            result = Err(ErrorList::new(vec![
8599                Error::invalid_field(DeclType::DebugProtocolRegistration, "source"),
8600            ])),
8601        },
8602
8603        // children
8604        test_validate_children_empty => {
8605            input = {
8606                let mut decl = new_component_decl();
8607                decl.children = Some(vec![fdecl::Child{
8608                    name: None,
8609                    url: None,
8610                    startup: None,
8611                    on_terminate: None,
8612                    environment: None,
8613                    ..Default::default()
8614                }]);
8615                decl
8616            },
8617            result = Err(ErrorList::new(vec![
8618                Error::missing_field(DeclType::Child, "name"),
8619                Error::missing_field(DeclType::Child, "url"),
8620                Error::missing_field(DeclType::Child, "startup"),
8621                // `on_terminate` is allowed to be None
8622            ])),
8623        },
8624        test_validate_children_invalid_identifiers => {
8625            input = {
8626                let mut decl = new_component_decl();
8627                decl.children = Some(vec![fdecl::Child{
8628                    name: Some("^bad".to_string()),
8629                    url: Some("scheme://invalid-port:99999999/path#frag".to_string()),
8630                    startup: Some(fdecl::StartupMode::Lazy),
8631                    on_terminate: None,
8632                    environment: None,
8633                    ..Default::default()
8634                }]);
8635                decl
8636            },
8637            result = Err(ErrorList::new(vec![
8638                Error::invalid_field(DeclType::Child, "name"),
8639                Error::invalid_url(DeclType::Child, "url", "\"scheme://invalid-port:99999999/path#frag\": Malformed URL: InvalidPort."),
8640            ])),
8641        },
8642        test_validate_children_long_identifiers => {
8643            input = {
8644                let mut decl = new_component_decl();
8645                decl.children = Some(vec![fdecl::Child{
8646                    name: Some("a".repeat(1025)),
8647                    url: Some(format!("fuchsia-pkg://{}", "a".repeat(4083))),
8648                    startup: Some(fdecl::StartupMode::Lazy),
8649                    on_terminate: None,
8650                    environment: Some("a".repeat(1025)),
8651                    ..Default::default()
8652                }]);
8653                decl
8654            },
8655            result = Err(ErrorList::new(vec![
8656                Error::field_too_long(DeclType::Child, "name"),
8657                Error::field_too_long(DeclType::Child, "url"),
8658                Error::field_too_long(DeclType::Child, "environment"),
8659                Error::invalid_environment(DeclType::Child, "environment", "a".repeat(1025)),
8660            ])),
8661        },
8662        test_validate_child_references_unknown_env => {
8663            input = {
8664                let mut decl = new_component_decl();
8665                decl.children = Some(vec![fdecl::Child{
8666                    name: Some("foo".to_string()),
8667                    url: Some("fuchsia-pkg://foo".to_string()),
8668                    startup: Some(fdecl::StartupMode::Lazy),
8669                    on_terminate: None,
8670                    environment: Some("test_env".to_string()),
8671                    ..Default::default()
8672                }]);
8673                decl
8674            },
8675            result = Err(ErrorList::new(vec![
8676                Error::invalid_environment(DeclType::Child, "environment", "test_env"),
8677            ])),
8678        },
8679
8680        // collections
8681        test_validate_collections_empty => {
8682            input = {
8683                let mut decl = new_component_decl();
8684                decl.collections = Some(vec![fdecl::Collection{
8685                    name: None,
8686                    durability: None,
8687                    environment: None,
8688                    allowed_offers: None,
8689                    allow_long_names: None,
8690                    ..Default::default()
8691                }]);
8692                decl
8693            },
8694            result = Err(ErrorList::new(vec![
8695                Error::missing_field(DeclType::Collection, "name"),
8696                Error::missing_field(DeclType::Collection, "durability"),
8697            ])),
8698        },
8699        test_validate_collections_invalid_identifiers => {
8700            input = {
8701                let mut decl = new_component_decl();
8702                decl.collections = Some(vec![fdecl::Collection{
8703                    name: Some("^bad".to_string()),
8704                    durability: Some(fdecl::Durability::Transient),
8705                    environment: None,
8706                    allowed_offers: Some(fdecl::AllowedOffers::StaticOnly),
8707                    allow_long_names: None,
8708                    ..Default::default()
8709                }]);
8710                decl
8711            },
8712            result = Err(ErrorList::new(vec![
8713                Error::invalid_field(DeclType::Collection, "name"),
8714            ])),
8715        },
8716        test_validate_collections_long_identifiers => {
8717            input = {
8718                let mut decl = new_component_decl();
8719                decl.collections = Some(vec![fdecl::Collection{
8720                    name: Some("a".repeat(1025)),
8721                    durability: Some(fdecl::Durability::Transient),
8722                    environment: None,
8723                    allowed_offers: Some(fdecl::AllowedOffers::StaticOnly),
8724                    allow_long_names: None,
8725                    ..Default::default()
8726                }]);
8727                decl
8728            },
8729            result = Err(ErrorList::new(vec![
8730                Error::field_too_long(DeclType::Collection, "name"),
8731            ])),
8732        },
8733        test_validate_collection_references_unknown_env => {
8734            input = {
8735                let mut decl = new_component_decl();
8736                decl.collections = Some(vec![fdecl::Collection {
8737                    name: Some("foo".to_string()),
8738                    durability: Some(fdecl::Durability::Transient),
8739                    environment: Some("test_env".to_string()),
8740                    allowed_offers: Some(fdecl::AllowedOffers::StaticOnly),
8741                    allow_long_names: None,
8742                    ..Default::default()
8743                }]);
8744                decl
8745            },
8746            result = Err(ErrorList::new(vec![
8747                Error::invalid_environment(DeclType::Collection, "environment", "test_env"),
8748            ])),
8749        },
8750
8751        // capabilities
8752        test_validate_capabilities_empty => {
8753            input = {
8754                let mut decl = new_component_decl();
8755                decl.capabilities = Some(vec![
8756                    fdecl::Capability::Service(fdecl::Service {
8757                        name: None,
8758                        source_path: None,
8759                        ..Default::default()
8760                    }),
8761                    fdecl::Capability::Protocol(fdecl::Protocol {
8762                        name: None,
8763                        source_path: None,
8764                        ..Default::default()
8765                    }),
8766                    fdecl::Capability::Directory(fdecl::Directory {
8767                        name: None,
8768                        source_path: None,
8769                        rights: None,
8770                        ..Default::default()
8771                    }),
8772                    fdecl::Capability::Storage(fdecl::Storage {
8773                        name: None,
8774                        source: None,
8775                        backing_dir: None,
8776                        subdir: None,
8777                        storage_id: None,
8778                        ..Default::default()
8779                    }),
8780                    fdecl::Capability::Runner(fdecl::Runner {
8781                        name: None,
8782                        source_path: None,
8783                        ..Default::default()
8784                    }),
8785                    fdecl::Capability::Resolver(fdecl::Resolver {
8786                        name: None,
8787                        source_path: None,
8788                        ..Default::default()
8789                    }),
8790                    fdecl::Capability::Dictionary(fdecl::Dictionary {
8791                        ..Default::default()
8792                    }),
8793                ]);
8794                decl
8795            },
8796            result = Err(ErrorList::new(vec![
8797                Error::missing_field(DeclType::Dictionary, "name"),
8798                Error::missing_field(DeclType::Service, "name"),
8799                Error::missing_field(DeclType::Service, "source_path"),
8800                Error::missing_field(DeclType::Protocol, "name"),
8801                Error::missing_field(DeclType::Protocol, "source_path"),
8802                Error::missing_field(DeclType::Directory, "name"),
8803                Error::missing_field(DeclType::Directory, "source_path"),
8804                Error::missing_field(DeclType::Directory, "rights"),
8805                Error::missing_field(DeclType::Storage, "source"),
8806                Error::missing_field(DeclType::Storage, "name"),
8807                Error::missing_field(DeclType::Storage, "storage_id"),
8808                Error::missing_field(DeclType::Storage, "backing_dir"),
8809                Error::missing_field(DeclType::Runner, "name"),
8810                Error::missing_field(DeclType::Runner, "source_path"),
8811                Error::missing_field(DeclType::Resolver, "name"),
8812                Error::missing_field(DeclType::Resolver, "source_path"),
8813            ])),
8814        },
8815        test_validate_capabilities_invalid_identifiers => {
8816            input = {
8817                let mut decl = new_component_decl();
8818                decl.capabilities = Some(vec![
8819                    fdecl::Capability::Service(fdecl::Service {
8820                        name: Some("^bad".to_string()),
8821                        source_path: Some("&bad".to_string()),
8822                        ..Default::default()
8823                    }),
8824                    fdecl::Capability::Protocol(fdecl::Protocol {
8825                        name: Some("^bad".to_string()),
8826                        source_path: Some("&bad".to_string()),
8827                        ..Default::default()
8828                    }),
8829                    fdecl::Capability::Directory(fdecl::Directory {
8830                        name: Some("^bad".to_string()),
8831                        source_path: Some("&bad".to_string()),
8832                        rights: Some(fio::Operations::CONNECT),
8833                        ..Default::default()
8834                    }),
8835                    fdecl::Capability::Storage(fdecl::Storage {
8836                        name: Some("^bad".to_string()),
8837                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
8838                            name: "/bad".to_string()
8839                        })),
8840                        backing_dir: Some("&bad".to_string()),
8841                        subdir: None,
8842                        storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker),
8843                        ..Default::default()
8844                    }),
8845                    fdecl::Capability::Runner(fdecl::Runner {
8846                        name: Some("^bad".to_string()),
8847                        source_path: Some("&bad".to_string()),
8848                        ..Default::default()
8849                    }),
8850                    fdecl::Capability::Resolver(fdecl::Resolver {
8851                        name: Some("^bad".to_string()),
8852                        source_path: Some("&bad".to_string()),
8853                        ..Default::default()
8854                    }),
8855                    fdecl::Capability::Dictionary(fdecl::Dictionary {
8856                        name: Some("^bad".to_string()),
8857                        ..Default::default()
8858                    }),
8859                ]);
8860                decl
8861            },
8862            result = Err(ErrorList::new(vec![
8863                Error::invalid_field(DeclType::Dictionary, "name"),
8864                Error::invalid_field(DeclType::Service, "name"),
8865                Error::invalid_field(DeclType::Service, "source_path"),
8866                Error::invalid_field(DeclType::Protocol, "name"),
8867                Error::invalid_field(DeclType::Protocol, "source_path"),
8868                Error::invalid_field(DeclType::Directory, "name"),
8869                Error::invalid_field(DeclType::Directory, "source_path"),
8870                Error::invalid_field(DeclType::Storage, "source"),
8871                Error::invalid_field(DeclType::Storage, "name"),
8872                Error::invalid_field(DeclType::Storage, "backing_dir"),
8873                Error::invalid_field(DeclType::Runner, "name"),
8874                Error::invalid_field(DeclType::Runner, "source_path"),
8875                Error::invalid_field(DeclType::Resolver, "name"),
8876                Error::invalid_field(DeclType::Resolver, "source_path"),
8877            ])),
8878        },
8879        test_validate_capabilities_invalid_child => {
8880            input = {
8881                let mut decl = new_component_decl();
8882                decl.capabilities = Some(vec![
8883                    fdecl::Capability::Storage(fdecl::Storage {
8884                        name: Some("foo".to_string()),
8885                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
8886                            name: "invalid".to_string(),
8887                        })),
8888                        backing_dir: Some("foo".to_string()),
8889                        subdir: None,
8890                        storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker),
8891                        ..Default::default()
8892                    }),
8893                ]);
8894                decl
8895            },
8896            result = Err(ErrorList::new(vec![
8897                Error::invalid_field(DeclType::Storage, "source"),
8898            ])),
8899        },
8900        test_validate_capabilities_long_identifiers => {
8901            input = {
8902                let mut decl = new_component_decl();
8903                decl.capabilities = Some(vec![
8904                    fdecl::Capability::Service(fdecl::Service {
8905                        name: Some("a".repeat(256)),
8906                        source_path: Some("/c".repeat(2048)),
8907                        ..Default::default()
8908                    }),
8909                    fdecl::Capability::Protocol(fdecl::Protocol {
8910                        name: Some("a".repeat(256)),
8911                        source_path: Some("/c".repeat(2048)),
8912                        ..Default::default()
8913                    }),
8914                    fdecl::Capability::Directory(fdecl::Directory {
8915                        name: Some("a".repeat(256)),
8916                        source_path: Some("/c".repeat(2048)),
8917                        rights: Some(fio::Operations::CONNECT),
8918                        ..Default::default()
8919                    }),
8920                    fdecl::Capability::Storage(fdecl::Storage {
8921                        name: Some("a".repeat(256)),
8922                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
8923                            name: "b".repeat(256),
8924                            collection: None,
8925                        })),
8926                        backing_dir: Some(format!("{}", "c".repeat(256))),
8927                        subdir: None,
8928                        storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker),
8929                        ..Default::default()
8930                    }),
8931                    fdecl::Capability::Runner(fdecl::Runner {
8932                        name: Some("a".repeat(256)),
8933                        source_path: Some("/c".repeat(2048)),
8934                        ..Default::default()
8935                    }),
8936                    fdecl::Capability::Resolver(fdecl::Resolver {
8937                        name: Some("a".repeat(256)),
8938                        source_path: Some("/c".repeat(2048)),
8939                        ..Default::default()
8940                    }),
8941                    fdecl::Capability::Dictionary(fdecl::Dictionary {
8942                        name: Some("a".repeat(256)),
8943                        ..Default::default()
8944                    }),
8945                ]);
8946                decl
8947            },
8948            result = Err(ErrorList::new(vec![
8949                Error::field_too_long(DeclType::Dictionary, "name"),
8950                Error::field_too_long(DeclType::Service, "name"),
8951                Error::field_too_long(DeclType::Service, "source_path"),
8952                Error::field_too_long(DeclType::Protocol, "name"),
8953                Error::field_too_long(DeclType::Protocol, "source_path"),
8954                Error::field_too_long(DeclType::Directory, "name"),
8955                Error::field_too_long(DeclType::Directory, "source_path"),
8956                Error::field_too_long(DeclType::Storage, "source.child.name"),
8957                Error::field_too_long(DeclType::Storage, "name"),
8958                Error::field_too_long(DeclType::Storage, "backing_dir"),
8959                Error::field_too_long(DeclType::Runner, "name"),
8960                Error::field_too_long(DeclType::Runner, "source_path"),
8961                Error::field_too_long(DeclType::Resolver, "name"),
8962                Error::field_too_long(DeclType::Resolver, "source_path"),
8963            ])),
8964        },
8965        test_validate_capabilities_duplicate_name => {
8966            input = {
8967                let mut decl = new_component_decl();
8968                decl.capabilities = Some(vec![
8969                    fdecl::Capability::Service(fdecl::Service {
8970                        name: Some("service".to_string()),
8971                        source_path: Some("/service".to_string()),
8972                        ..Default::default()
8973                    }),
8974                    fdecl::Capability::Service(fdecl::Service {
8975                        name: Some("service".to_string()),
8976                        source_path: Some("/service".to_string()),
8977                        ..Default::default()
8978                    }),
8979                    fdecl::Capability::Protocol(fdecl::Protocol {
8980                        name: Some("protocol".to_string()),
8981                        source_path: Some("/protocol".to_string()),
8982                        ..Default::default()
8983                    }),
8984                    fdecl::Capability::Protocol(fdecl::Protocol {
8985                        name: Some("protocol".to_string()),
8986                        source_path: Some("/protocol".to_string()),
8987                        ..Default::default()
8988                    }),
8989                    fdecl::Capability::Directory(fdecl::Directory {
8990                        name: Some("directory".to_string()),
8991                        source_path: Some("/directory".to_string()),
8992                        rights: Some(fio::Operations::CONNECT),
8993                        ..Default::default()
8994                    }),
8995                    fdecl::Capability::Directory(fdecl::Directory {
8996                        name: Some("directory".to_string()),
8997                        source_path: Some("/directory".to_string()),
8998                        rights: Some(fio::Operations::CONNECT),
8999                        ..Default::default()
9000                    }),
9001                    fdecl::Capability::Storage(fdecl::Storage {
9002                        name: Some("storage".to_string()),
9003                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
9004                        backing_dir: Some("directory".to_string()),
9005                        subdir: None,
9006                        storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker),
9007                        ..Default::default()
9008                    }),
9009                    fdecl::Capability::Storage(fdecl::Storage {
9010                        name: Some("storage".to_string()),
9011                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
9012                        backing_dir: Some("directory".to_string()),
9013                        subdir: None,
9014                        storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker),
9015                        ..Default::default()
9016                    }),
9017                    fdecl::Capability::Runner(fdecl::Runner {
9018                        name: Some("runner".to_string()),
9019                        source_path: Some("/runner".to_string()),
9020                        ..Default::default()
9021                    }),
9022                    fdecl::Capability::Runner(fdecl::Runner {
9023                        name: Some("runner".to_string()),
9024                        source_path: Some("/runner".to_string()),
9025                        ..Default::default()
9026                    }),
9027                    fdecl::Capability::Resolver(fdecl::Resolver {
9028                        name: Some("resolver".to_string()),
9029                        source_path: Some("/resolver".to_string()),
9030                        ..Default::default()
9031                    }),
9032                    fdecl::Capability::Resolver(fdecl::Resolver {
9033                        name: Some("resolver".to_string()),
9034                        source_path: Some("/resolver".to_string()),
9035                        ..Default::default()
9036                    }),
9037                    fdecl::Capability::Dictionary(fdecl::Dictionary {
9038                        name: Some("dictionary".to_string()),
9039                        ..Default::default()
9040                    }),
9041                    fdecl::Capability::Dictionary(fdecl::Dictionary {
9042                        name: Some("dictionary".to_string()),
9043                        ..Default::default()
9044                    }),
9045                ]);
9046                decl
9047            },
9048            result = Err(ErrorList::new(vec![
9049                Error::duplicate_field(DeclType::Dictionary, "name", "dictionary"),
9050                Error::duplicate_field(DeclType::Service, "name", "service"),
9051                Error::duplicate_field(DeclType::Protocol, "name", "protocol"),
9052                Error::duplicate_field(DeclType::Directory, "name", "directory"),
9053                Error::duplicate_field(DeclType::Storage, "name", "storage"),
9054                Error::duplicate_field(DeclType::Runner, "name", "runner"),
9055                Error::duplicate_field(DeclType::Resolver, "name", "resolver"),
9056            ])),
9057        },
9058        test_validate_invalid_service_aggregation_conflicting_filter => {
9059            input = {
9060                let mut decl = new_component_decl();
9061                decl.offers = Some(vec![
9062                    fdecl::Offer::Service(fdecl::OfferService {
9063                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
9064                            name: "coll_a".to_string()
9065                        })),
9066                        source_name: Some("fuchsia.logger.Log".to_string()),
9067                        target: Some(fdecl::Ref::Child(
9068                            fdecl::ChildRef {
9069                                name: "child_c".to_string(),
9070                                collection: None,
9071                            }
9072                        )),
9073                        target_name: Some("fuchsia.logger.Log1".to_string()),
9074                        source_instance_filter: Some(vec!["default".to_string()]),
9075                        ..Default::default()
9076                    }),
9077                    fdecl::Offer::Service(fdecl::OfferService {
9078                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
9079                            name: "coll_b".to_string()
9080                        })),
9081                        source_name: Some("fuchsia.logger.Log".to_string()),
9082                        target: Some(fdecl::Ref::Child(
9083                            fdecl::ChildRef {
9084                                name: "child_c".to_string(),
9085                                collection: None,
9086                            }
9087                        )),
9088                        target_name: Some("fuchsia.logger.Log1".to_string()),
9089                        source_instance_filter: Some(vec!["default".to_string()]),
9090                        ..Default::default()
9091                    }),
9092                ]);
9093                decl.collections = Some(vec![
9094                    fdecl::Collection {
9095                        name: Some("coll_a".to_string()),
9096                        durability: Some(fdecl::Durability::Transient),
9097                        ..Default::default()
9098                    },
9099                    fdecl::Collection {
9100                        name: Some("coll_b".to_string()),
9101                        durability: Some(fdecl::Durability::Transient),
9102                        ..Default::default()
9103                    },
9104                ]);
9105                decl.children = Some(vec![
9106                    fdecl::Child {
9107                        name: Some("child_c".to_string()),
9108                        url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
9109                        startup: Some(fdecl::StartupMode::Lazy),
9110                        ..Default::default()
9111                    },
9112                ]);
9113                decl
9114            },
9115            result = Err(ErrorList::new(vec![
9116                Error::invalid_aggregate_offer("Conflicting source_instance_filter in aggregate \
9117                   service offer, instance_name 'default' seen in filter lists multiple times"),
9118            ])),
9119        },
9120
9121        test_validate_invalid_service_aggregation_conflicting_source_name => {
9122            input = {
9123                let mut decl = new_component_decl();
9124                decl.offers = Some(vec![
9125                    fdecl::Offer::Service(fdecl::OfferService {
9126                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
9127                            name: "coll_a".into()
9128                        })),
9129                        source_name: Some("fuchsia.logger.Log".to_string()),
9130                        target: Some(fdecl::Ref::Child(
9131                            fdecl::ChildRef {
9132                                name: "child_c".to_string(),
9133                                collection: None,
9134                            }
9135                        )),
9136                        target_name: Some("fuchsia.logger.Log2".to_string()),
9137                        source_instance_filter: Some(vec!["default2".to_string()]),
9138                        ..Default::default()
9139                    }),
9140                    fdecl::Offer::Service(fdecl::OfferService {
9141                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
9142                            name: "coll_b".into()
9143                        })),
9144                        source_name: Some("fuchsia.logger.LogAlt".to_string()),
9145                        target: Some(fdecl::Ref::Child(
9146                            fdecl::ChildRef {
9147                                name: "child_c".to_string(),
9148                                collection: None,
9149                            }
9150                        )),
9151                        target_name: Some("fuchsia.logger.Log2".to_string()),
9152                        source_instance_filter: Some(vec!["default".to_string()]),
9153                        ..Default::default()
9154                    })
9155                ]);
9156                decl.collections = Some(vec![
9157                    fdecl::Collection {
9158                        name: Some("coll_a".to_string()),
9159                        durability: Some(fdecl::Durability::Transient),
9160                        ..Default::default()
9161                    },
9162                    fdecl::Collection {
9163                        name: Some("coll_b".to_string()),
9164                        durability: Some(fdecl::Durability::Transient),
9165                        ..Default::default()
9166                    },
9167                ]);
9168                decl.children = Some(vec![
9169                    fdecl::Child {
9170                        name: Some("child_c".to_string()),
9171                        url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
9172                        startup: Some(fdecl::StartupMode::Lazy),
9173                        on_terminate: None,
9174                        environment: None,
9175                        ..Default::default()
9176                    },
9177                ]);
9178                decl
9179            },
9180            result = Err(ErrorList::new(vec![
9181                Error::invalid_aggregate_offer("All aggregate service offers must have the same source_name, saw fuchsia.logger.Log, fuchsia.logger.LogAlt. Use renamed_instances to rename instance names to avoid conflict."),
9182            ])),
9183        },
9184
9185        test_validate_resolvers_missing_from_offer => {
9186            input = {
9187                let mut decl = new_component_decl();
9188                decl.offers = Some(vec![fdecl::Offer::Resolver(fdecl::OfferResolver {
9189                    source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
9190                    source_name: Some("a".to_string()),
9191                    target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child".to_string(), collection: None })),
9192                    target_name: Some("a".to_string()),
9193                    ..Default::default()
9194                })]);
9195                decl.children = Some(vec![fdecl::Child {
9196                    name: Some("child".to_string()),
9197                    url: Some("test:///child".to_string()),
9198                    startup: Some(fdecl::StartupMode::Eager),
9199                    on_terminate: None,
9200                    environment: None,
9201                    ..Default::default()
9202                }]);
9203                decl
9204            },
9205            result = Err(ErrorList::new(vec![
9206                Error::invalid_capability(DeclType::OfferResolver, "source", "a"),
9207            ])),
9208        },
9209        test_validate_resolvers_missing_from_expose => {
9210            input = {
9211                let mut decl = new_component_decl();
9212                decl.exposes = Some(vec![fdecl::Expose::Resolver(fdecl::ExposeResolver {
9213                    source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
9214                    source_name: Some("a".to_string()),
9215                    target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
9216                    target_name: Some("a".to_string()),
9217                    ..Default::default()
9218                })]);
9219                decl
9220            },
9221            result = Err(ErrorList::new(vec![
9222                Error::invalid_capability(DeclType::ExposeResolver, "source", "a"),
9223            ])),
9224        },
9225
9226        test_validate_config_missing_config => {
9227            input = {
9228                let mut decl = new_component_decl();
9229                decl.config = Some(fdecl::ConfigSchema{
9230                    fields: None,
9231                    checksum: None,
9232                    value_source: None,
9233                    ..Default::default()
9234                });
9235                decl
9236            },
9237            result = Err(ErrorList::new(vec![
9238                Error::missing_field(DeclType::ConfigSchema, "fields"),
9239                Error::missing_field(DeclType::ConfigSchema, "checksum"),
9240                Error::missing_field(DeclType::ConfigSchema, "value_source"),
9241            ])),
9242        },
9243
9244        test_validate_config_missing_config_field => {
9245            input = {
9246                let mut decl = new_component_decl();
9247                decl.config = Some(fdecl::ConfigSchema{
9248                    fields: Some(vec![
9249                        fdecl::ConfigField {
9250                            key: None,
9251                            type_: None,
9252                            ..Default::default()
9253                        }
9254                    ]),
9255                    checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])),
9256                    value_source: Some(fdecl::ConfigValueSource::PackagePath("config/test.cvf".to_string())),
9257                    ..Default::default()
9258                });
9259                decl
9260            },
9261            result = Err(ErrorList::new(vec![
9262                Error::missing_field(DeclType::ConfigField, "key"),
9263                Error::missing_field(DeclType::ConfigField, "value_type"),
9264            ])),
9265        },
9266
9267        test_validate_config_bool => {
9268            input = {
9269                let mut decl = new_component_decl();
9270                decl.config = Some(fdecl::ConfigSchema{
9271                    fields: Some(vec![
9272                        fdecl::ConfigField {
9273                            key: Some("test".to_string()),
9274                            type_: Some(fdecl::ConfigType {
9275                                layout: fdecl::ConfigTypeLayout::Bool,
9276                                parameters: Some(vec![]),
9277                                constraints: vec![]
9278                            }),
9279                            ..Default::default()
9280                        }
9281                    ]),
9282                    checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])),
9283                    value_source: Some(fdecl::ConfigValueSource::PackagePath("config/test.cvf".to_string())),
9284                    ..Default::default()
9285                });
9286                decl
9287            },
9288            result = Ok(()),
9289        },
9290
9291        test_validate_config_bool_extra_constraint => {
9292            input = {
9293                let mut decl = new_component_decl();
9294                decl.config = Some(fdecl::ConfigSchema{
9295                    fields: Some(vec![
9296                        fdecl::ConfigField {
9297                            key: Some("test".to_string()),
9298                            type_: Some(fdecl::ConfigType {
9299                                layout: fdecl::ConfigTypeLayout::Bool,
9300                                parameters: Some(vec![]),
9301                                constraints: vec![fdecl::LayoutConstraint::MaxSize(10)]
9302                            }),
9303                            ..Default::default()
9304                        }
9305                    ]),
9306                    checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])),
9307                    value_source: Some(fdecl::ConfigValueSource::PackagePath("config/test.cvf".to_string())),
9308                    ..Default::default()
9309                });
9310                decl
9311            },
9312            result = Err(ErrorList::new(vec![
9313                Error::extraneous_field(DeclType::ConfigType, "constraints")
9314            ])),
9315        },
9316
9317        test_validate_config_bool_missing_parameters => {
9318            input = {
9319                let mut decl = new_component_decl();
9320                decl.config = Some(fdecl::ConfigSchema{
9321                    fields: Some(vec![
9322                        fdecl::ConfigField {
9323                            key: Some("test".to_string()),
9324                            type_: Some(fdecl::ConfigType {
9325                                layout: fdecl::ConfigTypeLayout::Bool,
9326                                parameters: None,
9327                                constraints: vec![]
9328                            }),
9329                            ..Default::default()
9330                        }
9331                    ]),
9332                    checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])),
9333                    value_source: Some(fdecl::ConfigValueSource::PackagePath("config/test.cvf".to_string())),
9334                    ..Default::default()
9335                });
9336                decl
9337            },
9338            result = Err(ErrorList::new(vec![
9339                Error::missing_field(DeclType::ConfigType, "parameters")
9340            ])),
9341        },
9342
9343        test_validate_config_string => {
9344            input = {
9345                let mut decl = new_component_decl();
9346                decl.config = Some(fdecl::ConfigSchema{
9347                    fields: Some(vec![
9348                        fdecl::ConfigField {
9349                            key: Some("test".to_string()),
9350                            type_: Some(fdecl::ConfigType {
9351                                layout: fdecl::ConfigTypeLayout::String,
9352                                parameters: Some(vec![]),
9353                                constraints: vec![fdecl::LayoutConstraint::MaxSize(10)]
9354                            }),
9355                            ..Default::default()
9356                        }
9357                    ]),
9358                    checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])),
9359                    value_source: Some(fdecl::ConfigValueSource::PackagePath("config/test.cvf".to_string())),
9360                    ..Default::default()
9361                });
9362                decl
9363            },
9364            result = Ok(()),
9365        },
9366
9367        test_validate_config_string_missing_parameter => {
9368            input = {
9369                let mut decl = new_component_decl();
9370                decl.config = Some(fdecl::ConfigSchema{
9371                    fields: Some(vec![
9372                        fdecl::ConfigField {
9373                            key: Some("test".to_string()),
9374                            type_: Some(fdecl::ConfigType {
9375                                layout: fdecl::ConfigTypeLayout::String,
9376                                parameters: None,
9377                                constraints: vec![fdecl::LayoutConstraint::MaxSize(10)]
9378                            }),
9379                            ..Default::default()
9380                        }
9381                    ]),
9382                    checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])),
9383                    value_source: Some(fdecl::ConfigValueSource::PackagePath("config/test.cvf".to_string())),
9384                    ..Default::default()
9385                });
9386                decl
9387            },
9388            result = Err(ErrorList::new(vec![
9389                Error::missing_field(DeclType::ConfigType, "parameters")
9390            ])),
9391        },
9392
9393        test_validate_config_string_missing_constraint => {
9394            input = {
9395                let mut decl = new_component_decl();
9396                decl.config = Some(fdecl::ConfigSchema{
9397                    fields: Some(vec![
9398                        fdecl::ConfigField {
9399                            key: Some("test".to_string()),
9400                            type_: Some(fdecl::ConfigType {
9401                                layout: fdecl::ConfigTypeLayout::String,
9402                                parameters: Some(vec![]),
9403                                constraints: vec![]
9404                            }),
9405                            ..Default::default()
9406                        }
9407                    ]),
9408                    checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])),
9409                    value_source: Some(fdecl::ConfigValueSource::PackagePath("config/test.cvf".to_string())),
9410                    ..Default::default()
9411                });
9412                decl
9413            },
9414            result = Err(ErrorList::new(vec![
9415                Error::missing_field(DeclType::ConfigType, "constraints")
9416            ])),
9417        },
9418
9419        test_validate_config_string_extra_constraint => {
9420            input = {
9421                let mut decl = new_component_decl();
9422                decl.config = Some(fdecl::ConfigSchema{
9423                    fields: Some(vec![
9424                        fdecl::ConfigField {
9425                            key: Some("test".to_string()),
9426                            type_: Some(fdecl::ConfigType {
9427                                layout: fdecl::ConfigTypeLayout::String,
9428                                parameters: Some(vec![]),
9429                                constraints: vec![fdecl::LayoutConstraint::MaxSize(10), fdecl::LayoutConstraint::MaxSize(10)]
9430                            }),
9431                            ..Default::default()
9432                        }
9433                    ]),
9434                    checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])),
9435                    value_source: Some(fdecl::ConfigValueSource::PackagePath("config/test.cvf".to_string())),
9436                    ..Default::default()
9437                });
9438                decl
9439            },
9440            result = Err(ErrorList::new(vec![
9441                Error::extraneous_field(DeclType::ConfigType, "constraints")
9442            ])),
9443        },
9444
9445        test_validate_config_vector_bool => {
9446            input = {
9447                let mut decl = new_component_decl();
9448                decl.config = Some(fdecl::ConfigSchema{
9449                    fields: Some(vec![
9450                        fdecl::ConfigField {
9451                            key: Some("test".to_string()),
9452                            type_: Some(fdecl::ConfigType {
9453                                layout: fdecl::ConfigTypeLayout::Vector,
9454                                parameters: Some(vec![fdecl::LayoutParameter::NestedType(fdecl::ConfigType {
9455                                    layout: fdecl::ConfigTypeLayout::Bool,
9456                                    parameters: Some(vec![]),
9457                                    constraints: vec![],
9458                                })]),
9459                                constraints: vec![fdecl::LayoutConstraint::MaxSize(10)]
9460                            }),
9461                            ..Default::default()
9462                        }
9463                    ]),
9464                    checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])),
9465                    value_source: Some(fdecl::ConfigValueSource::PackagePath("config/test.cvf".to_string())),
9466                    ..Default::default()
9467                });
9468                decl
9469            },
9470            result = Ok(()),
9471        },
9472
9473        test_validate_config_vector_extra_parameter => {
9474            input = {
9475                let mut decl = new_component_decl();
9476                decl.config = Some(fdecl::ConfigSchema{
9477                    fields: Some(vec![
9478                        fdecl::ConfigField {
9479                            key: Some("test".to_string()),
9480                            type_: Some(fdecl::ConfigType {
9481                                layout: fdecl::ConfigTypeLayout::Vector,
9482                                parameters: Some(vec![fdecl::LayoutParameter::NestedType(fdecl::ConfigType {
9483                                    layout: fdecl::ConfigTypeLayout::Bool,
9484                                    parameters: Some(vec![]),
9485                                    constraints: vec![],
9486                                }), fdecl::LayoutParameter::NestedType(fdecl::ConfigType {
9487                                    layout: fdecl::ConfigTypeLayout::Uint8,
9488                                    parameters: Some(vec![]),
9489                                    constraints: vec![],
9490                                })]),
9491                                constraints: vec![fdecl::LayoutConstraint::MaxSize(10)]
9492                            }),
9493                            ..Default::default()
9494                        }
9495                    ]),
9496                    checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])),
9497                    value_source: Some(fdecl::ConfigValueSource::PackagePath("config/test.cvf".to_string())),
9498                    ..Default::default()
9499                });
9500                decl
9501            },
9502            result = Err(ErrorList::new(vec![
9503                Error::extraneous_field(DeclType::ConfigType, "parameters")
9504            ])),
9505        },
9506
9507        test_validate_config_vector_missing_parameter => {
9508            input = {
9509                let mut decl = new_component_decl();
9510                decl.config = Some(fdecl::ConfigSchema{
9511                    fields: Some(vec![
9512                        fdecl::ConfigField {
9513                            key: Some("test".to_string()),
9514                            type_: Some(fdecl::ConfigType {
9515                                layout: fdecl::ConfigTypeLayout::Vector,
9516                                parameters: Some(vec![]),
9517                                constraints: vec![fdecl::LayoutConstraint::MaxSize(10)]
9518                            }),
9519                            ..Default::default()
9520                        }
9521                    ]),
9522                    checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])),
9523                    value_source: Some(fdecl::ConfigValueSource::PackagePath("config/test.cvf".to_string())),
9524                    ..Default::default()
9525                });
9526                decl
9527            },
9528            result = Err(ErrorList::new(vec![
9529                Error::missing_field(DeclType::ConfigType, "parameters")
9530            ])),
9531        },
9532
9533        test_validate_config_vector_string => {
9534            input = {
9535                let mut decl = new_component_decl();
9536                decl.config = Some(fdecl::ConfigSchema{
9537                    fields: Some(vec![
9538                        fdecl::ConfigField {
9539                            key: Some("test".to_string()),
9540                            type_: Some(fdecl::ConfigType {
9541                                layout: fdecl::ConfigTypeLayout::Vector,
9542                                parameters: Some(vec![fdecl::LayoutParameter::NestedType(fdecl::ConfigType {
9543                                    layout: fdecl::ConfigTypeLayout::String,
9544                                    parameters: Some(vec![]),
9545                                    constraints: vec![fdecl::LayoutConstraint::MaxSize(10)]
9546                                })]),
9547                                constraints: vec![fdecl::LayoutConstraint::MaxSize(10)]
9548                            }),
9549                            ..Default::default()
9550                        }
9551                    ]),
9552                    checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])),
9553                    value_source: Some(fdecl::ConfigValueSource::PackagePath("config/test.cvf".to_string())),
9554                    ..Default::default()
9555                });
9556                decl
9557            },
9558            result = Ok(()),
9559        },
9560
9561        test_validate_config_vector_vector => {
9562            input = {
9563                let mut decl = new_component_decl();
9564                decl.config = Some(fdecl::ConfigSchema{
9565                    fields: Some(vec![
9566                        fdecl::ConfigField {
9567                            key: Some("test".to_string()),
9568                            type_: Some(fdecl::ConfigType {
9569                                layout: fdecl::ConfigTypeLayout::Vector,
9570                                parameters: Some(vec![fdecl::LayoutParameter::NestedType(fdecl::ConfigType {
9571                                    layout: fdecl::ConfigTypeLayout::Vector,
9572                                    parameters: Some(vec![fdecl::LayoutParameter::NestedType(fdecl::ConfigType {
9573                                        layout: fdecl::ConfigTypeLayout::String,
9574                                        parameters: Some(vec![]),
9575                                        constraints: vec![fdecl::LayoutConstraint::MaxSize(10)]
9576                                    })]),
9577                                    constraints: vec![fdecl::LayoutConstraint::MaxSize(10)]
9578                                })]),
9579                                constraints: vec![fdecl::LayoutConstraint::MaxSize(10)]
9580                            }),
9581                            ..Default::default()
9582                        }
9583                    ]),
9584                    checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])),
9585                    value_source: Some(fdecl::ConfigValueSource::PackagePath("config/test.cvf".to_string())),
9586                    ..Default::default()
9587                });
9588                decl
9589            },
9590            result = Err(ErrorList::new(vec![
9591                Error::nested_vector()
9592            ])),
9593        },
9594
9595        test_validate_exposes_invalid_aggregation_different_availability => {
9596            input = {
9597                let mut decl = new_component_decl();
9598                decl.exposes = Some(vec![
9599                    fdecl::Expose::Service(fdecl::ExposeService {
9600                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
9601                            name: "coll_a".into()
9602                        })),
9603                        source_name: Some("fuchsia.logger.Log".to_string()),
9604                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
9605                        target_name: Some("fuchsia.logger.Log".to_string()),
9606                        availability: Some(fdecl::Availability::Required),
9607                        ..Default::default()
9608                    }),
9609                    fdecl::Expose::Service(fdecl::ExposeService {
9610                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
9611                            name: "coll_b".into()
9612                        })),
9613                        source_name: Some("fuchsia.logger.Log".to_string()),
9614                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
9615                        target_name: Some("fuchsia.logger.Log".to_string()),
9616                        availability: Some(fdecl::Availability::Optional),
9617                        ..Default::default()
9618                    })
9619                ]);
9620                decl.collections = Some(vec![
9621                    fdecl::Collection {
9622                        name: Some("coll_a".to_string()),
9623                        durability: Some(fdecl::Durability::Transient),
9624                        ..Default::default()
9625                    },
9626                    fdecl::Collection {
9627                        name: Some("coll_b".to_string()),
9628                        durability: Some(fdecl::Durability::Transient),
9629                        ..Default::default()
9630                    },
9631                ]);
9632                decl
9633            },
9634            result = Err(ErrorList::new(vec![
9635                Error::DifferentAvailabilityInAggregation(AvailabilityList(vec![
9636                    fdecl::Availability::Required,
9637                    fdecl::Availability::Optional,
9638                ]))
9639            ])),
9640        },
9641
9642        test_validate_offers_invalid_aggregation_different_availability => {
9643            input = {
9644                let mut decl = new_component_decl();
9645                decl.offers = Some(vec![
9646                    fdecl::Offer::Service(fdecl::OfferService {
9647                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
9648                            name: "coll_a".into()
9649                        })),
9650                        source_name: Some("fuchsia.logger.Log".to_string()),
9651                        target: Some(fdecl::Ref::Child(
9652                            fdecl::ChildRef {
9653                                name: "child_c".to_string(),
9654                                collection: None,
9655                            }
9656                        )),
9657                        target_name: Some("fuchsia.logger.Log".to_string()),
9658                        source_instance_filter: Some(vec!["default".to_string()]),
9659                        availability: Some(fdecl::Availability::Required),
9660                        ..Default::default()
9661                    }),
9662                    fdecl::Offer::Service(fdecl::OfferService {
9663                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
9664                            name: "coll_b".into()
9665                        })),
9666                        source_name: Some("fuchsia.logger.Log".to_string()),
9667                        target: Some(fdecl::Ref::Child(
9668                            fdecl::ChildRef {
9669                                name: "child_c".to_string(),
9670                                collection: None,
9671                            }
9672                        )),
9673                        target_name: Some("fuchsia.logger.Log".to_string()),
9674                        source_instance_filter: Some(vec!["a_different_default".to_string()]),
9675                        availability: Some(fdecl::Availability::Optional),
9676                        ..Default::default()
9677                    })
9678                ]);
9679                decl.collections = Some(vec![
9680                    fdecl::Collection {
9681                        name: Some("coll_a".to_string()),
9682                        durability: Some(fdecl::Durability::Transient),
9683                        ..Default::default()
9684                    },
9685                    fdecl::Collection {
9686                        name: Some("coll_b".to_string()),
9687                        durability: Some(fdecl::Durability::Transient),
9688                        ..Default::default()
9689                    },
9690                ]);
9691                decl.children = Some(vec![
9692                    fdecl::Child {
9693                        name: Some("child_c".to_string()),
9694                        url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
9695                        startup: Some(fdecl::StartupMode::Lazy),
9696                        on_terminate: None,
9697                        environment: None,
9698                        ..Default::default()
9699                    },
9700                ]);
9701                decl
9702            },
9703            result = Err(ErrorList::new(vec![
9704                Error::DifferentAvailabilityInAggregation(AvailabilityList(vec![
9705                    fdecl::Availability::Required,
9706                    fdecl::Availability::Optional,
9707                ]))
9708            ])),
9709        },
9710    }
9711
9712    test_validate_capabilities! {
9713        test_validate_capabilities_individually_ok => {
9714            input = vec![
9715                fdecl::Capability::Protocol(fdecl::Protocol {
9716                    name: Some("foo_svc".into()),
9717                    source_path: Some("/svc/foo".into()),
9718                    ..Default::default()
9719                }),
9720                fdecl::Capability::Directory(fdecl::Directory {
9721                    name: Some("foo_dir".into()),
9722                    source_path: Some("/foo".into()),
9723                    rights: Some(fio::Operations::CONNECT),
9724                    ..Default::default()
9725                }),
9726            ],
9727            as_builtin = false,
9728            result = Ok(()),
9729        },
9730        test_validate_capabilities_individually_err => {
9731            input = vec![
9732                fdecl::Capability::Protocol(fdecl::Protocol {
9733                    name: None,
9734                    source_path: None,
9735                    ..Default::default()
9736                }),
9737                fdecl::Capability::Directory(fdecl::Directory {
9738                    name: None,
9739                    source_path: None,
9740                    rights: None,
9741                    ..Default::default()
9742                }),
9743            ],
9744            as_builtin = false,
9745            result = Err(ErrorList::new(vec![
9746                Error::missing_field(DeclType::Protocol, "name"),
9747                Error::missing_field(DeclType::Protocol, "source_path"),
9748                Error::missing_field(DeclType::Directory, "name"),
9749                Error::missing_field(DeclType::Directory, "source_path"),
9750                Error::missing_field(DeclType::Directory, "rights"),
9751            ])),
9752        },
9753        test_validate_builtin_capabilities_individually_ok => {
9754            input = vec![
9755                fdecl::Capability::Protocol(fdecl::Protocol {
9756                    name: Some("foo_protocol".into()),
9757                    source_path: None,
9758                    ..Default::default()
9759                }),
9760                fdecl::Capability::Directory(fdecl::Directory {
9761                    name: Some("foo_dir".into()),
9762                    source_path: None,
9763                    rights: Some(fio::Operations::CONNECT),
9764                    ..Default::default()
9765                }),
9766                fdecl::Capability::Service(fdecl::Service {
9767                    name: Some("foo_svc".into()),
9768                    source_path: None,
9769                    ..Default::default()
9770                }),
9771                fdecl::Capability::Runner(fdecl::Runner {
9772                    name: Some("foo_runner".into()),
9773                    source_path: None,
9774                    ..Default::default()
9775                }),
9776                fdecl::Capability::Resolver(fdecl::Resolver {
9777                    name: Some("foo_resolver".into()),
9778                    source_path: None,
9779                    ..Default::default()
9780                }),
9781            ],
9782            as_builtin = true,
9783            result = Ok(()),
9784        },
9785        test_validate_builtin_capabilities_individually_err => {
9786            input = vec![
9787                fdecl::Capability::Protocol(fdecl::Protocol {
9788                    name: None,
9789                    source_path: Some("/svc/foo".into()),
9790                    ..Default::default()
9791                }),
9792                fdecl::Capability::Directory(fdecl::Directory {
9793                    name: None,
9794                    source_path: Some("/foo".into()),
9795                    rights: None,
9796                    ..Default::default()
9797                }),
9798                fdecl::Capability::Service(fdecl::Service {
9799                    name: None,
9800                    source_path: Some("/svc/foo".into()),
9801                    ..Default::default()
9802                }),
9803                fdecl::Capability::Runner(fdecl::Runner {
9804                    name: None,
9805                    source_path:  Some("/foo".into()),
9806                    ..Default::default()
9807                }),
9808                fdecl::Capability::Resolver(fdecl::Resolver {
9809                    name: None,
9810                    source_path:  Some("/foo".into()),
9811                    ..Default::default()
9812                }),
9813                fdecl::Capability::Storage(fdecl::Storage {
9814                    name: None,
9815                    ..Default::default()
9816                }),
9817            ],
9818            as_builtin = true,
9819            result = Err(ErrorList::new(vec![
9820                Error::missing_field(DeclType::Protocol, "name"),
9821                Error::extraneous_source_path(DeclType::Protocol, "/svc/foo"),
9822                Error::missing_field(DeclType::Directory, "name"),
9823                Error::extraneous_source_path(DeclType::Directory, "/foo"),
9824                Error::missing_field(DeclType::Directory, "rights"),
9825                Error::missing_field(DeclType::Service, "name"),
9826                Error::extraneous_source_path(DeclType::Service, "/svc/foo"),
9827                Error::missing_field(DeclType::Runner, "name"),
9828                Error::extraneous_source_path(DeclType::Runner, "/foo"),
9829                Error::missing_field(DeclType::Resolver, "name"),
9830                Error::extraneous_source_path(DeclType::Resolver, "/foo"),
9831                Error::CapabilityCannotBeBuiltin(DeclType::Storage),
9832            ])),
9833        },
9834        test_validate_delivery_type_ok => {
9835            input = vec![
9836                fdecl::Capability::Protocol(fdecl::Protocol {
9837                    name: Some("foo_svc1".into()),
9838                    source_path: Some("/svc/foo1".into()),
9839                    ..Default::default()
9840                }),
9841                fdecl::Capability::Protocol(fdecl::Protocol {
9842                    name: Some("foo_svc2".into()),
9843                    source_path: Some("/svc/foo2".into()),
9844                    delivery: Some(fdecl::DeliveryType::Immediate),
9845                    ..Default::default()
9846                }),
9847                fdecl::Capability::Protocol(fdecl::Protocol {
9848                    name: Some("foo_svc3".into()),
9849                    source_path: Some("/svc/foo3".into()),
9850                    delivery: Some(fdecl::DeliveryType::OnReadable),
9851                    ..Default::default()
9852                }),
9853            ],
9854            as_builtin = false,
9855            result = Ok(()),
9856        },
9857        test_validate_delivery_type_err => {
9858            input = vec![
9859                fdecl::Capability::Protocol(fdecl::Protocol {
9860                    name: Some("foo_svc".into()),
9861                    source_path: Some("/svc/foo".into()),
9862                    delivery: Some(fdecl::DeliveryType::unknown()),
9863                    ..Default::default()
9864                }),
9865            ],
9866            as_builtin = false,
9867            result = Err(ErrorList::new(vec![
9868                Error::invalid_field(DeclType::Protocol, "delivery"),
9869            ])),
9870        },
9871    }
9872
9873    /// Passes different source and availability options to `new_expose` to
9874    /// generate a component declaration.
9875    fn generate_expose_different_source_and_availability_decl(
9876        new_expose: impl Fn(fdecl::Ref, fdecl::Availability, &str) -> fdecl::Expose,
9877    ) -> fdecl::Component {
9878        let mut decl = new_component_decl();
9879        let child = "child";
9880        decl.children = Some(vec![fdecl::Child {
9881            name: Some(child.to_string()),
9882            url: Some("fuchsia-pkg://fuchsia.com/source#meta/source.cm".to_string()),
9883            startup: Some(fdecl::StartupMode::Lazy),
9884            ..Default::default()
9885        }]);
9886        decl.exposes = Some(vec![
9887            // Optional expose from self is okay.
9888            new_expose(
9889                fdecl::Ref::Self_(fdecl::SelfRef {}),
9890                fdecl::Availability::Optional,
9891                "fuchsia.examples.Echo1",
9892            ),
9893            // Optional expose from child is okay.
9894            new_expose(
9895                fdecl::Ref::Child(fdecl::ChildRef { name: child.to_string(), collection: None }),
9896                fdecl::Availability::Optional,
9897                "fuchsia.examples.Echo2",
9898            ),
9899            // Optional expose from void is okay.
9900            new_expose(
9901                fdecl::Ref::VoidType(fdecl::VoidRef {}),
9902                fdecl::Availability::Optional,
9903                "fuchsia.examples.Echo3",
9904            ),
9905            // Optional expose from framework is okay.
9906            new_expose(
9907                fdecl::Ref::Framework(fdecl::FrameworkRef {}),
9908                fdecl::Availability::Optional,
9909                "fuchsia.examples.Echo4",
9910            ),
9911            // Transitional expose from self is okay.
9912            new_expose(
9913                fdecl::Ref::Self_(fdecl::SelfRef {}),
9914                fdecl::Availability::Transitional,
9915                "fuchsia.examples.Echo5",
9916            ),
9917            // Transitional expose from child is okay.
9918            new_expose(
9919                fdecl::Ref::Child(fdecl::ChildRef { name: child.to_string(), collection: None }),
9920                fdecl::Availability::Transitional,
9921                "fuchsia.examples.Echo6",
9922            ),
9923            // Transitional expose from void is okay.
9924            new_expose(
9925                fdecl::Ref::VoidType(fdecl::VoidRef {}),
9926                fdecl::Availability::Transitional,
9927                "fuchsia.examples.Echo7",
9928            ),
9929            // Transitional expose from framework is okay.
9930            new_expose(
9931                fdecl::Ref::Framework(fdecl::FrameworkRef {}),
9932                fdecl::Availability::Transitional,
9933                "fuchsia.examples.Echo8",
9934            ),
9935            // Same-as-target expose from self is okay.
9936            new_expose(
9937                fdecl::Ref::Self_(fdecl::SelfRef {}),
9938                fdecl::Availability::SameAsTarget,
9939                "fuchsia.examples.Echo9",
9940            ),
9941            // Same-as-target expose from child is okay.
9942            new_expose(
9943                fdecl::Ref::Child(fdecl::ChildRef { name: child.to_string(), collection: None }),
9944                fdecl::Availability::SameAsTarget,
9945                "fuchsia.examples.Echo10",
9946            ),
9947            // Same-as-target expose from framework is okay.
9948            new_expose(
9949                fdecl::Ref::Framework(fdecl::FrameworkRef {}),
9950                fdecl::Availability::SameAsTarget,
9951                "fuchsia.examples.Echo11",
9952            ),
9953            // Required expose from void is an error.
9954            new_expose(
9955                fdecl::Ref::VoidType(fdecl::VoidRef {}),
9956                fdecl::Availability::Required,
9957                "fuchsia.examples.Echo12",
9958            ),
9959            // Same-as-target expose from void is an error.
9960            new_expose(
9961                fdecl::Ref::VoidType(fdecl::VoidRef {}),
9962                fdecl::Availability::SameAsTarget,
9963                "fuchsia.examples.Echo13",
9964            ),
9965        ]);
9966        decl
9967    }
9968
9969    /// Passes different source and availability options to `new_offer` to
9970    /// generate a component declaration.
9971    fn generate_offer_different_source_and_availability_decl(
9972        new_offer: impl Fn(fdecl::Ref, fdecl::Availability, &str) -> fdecl::Offer,
9973    ) -> fdecl::Component {
9974        let mut decl = new_component_decl();
9975        decl.children = Some(vec![
9976            fdecl::Child {
9977                name: Some("source".to_string()),
9978                url: Some("fuchsia-pkg://fuchsia.com/source#meta/source.cm".to_string()),
9979                startup: Some(fdecl::StartupMode::Lazy),
9980                on_terminate: None,
9981                environment: None,
9982                ..Default::default()
9983            },
9984            fdecl::Child {
9985                name: Some("sink".to_string()),
9986                url: Some("fuchsia-pkg://fuchsia.com/sink#meta/sink.cm".to_string()),
9987                startup: Some(fdecl::StartupMode::Lazy),
9988                on_terminate: None,
9989                environment: None,
9990                ..Default::default()
9991            },
9992        ]);
9993        decl.offers = Some(vec![
9994            // These offers are fine, offers with a source of parent or void can be
9995            // optional.
9996            new_offer(
9997                fdecl::Ref::Parent(fdecl::ParentRef {}),
9998                fdecl::Availability::Required,
9999                "fuchsia.examples.Echo0",
10000            ),
10001            new_offer(
10002                fdecl::Ref::Parent(fdecl::ParentRef {}),
10003                fdecl::Availability::Optional,
10004                "fuchsia.examples.Echo1",
10005            ),
10006            new_offer(
10007                fdecl::Ref::Parent(fdecl::ParentRef {}),
10008                fdecl::Availability::SameAsTarget,
10009                "fuchsia.examples.Echo2",
10010            ),
10011            new_offer(
10012                fdecl::Ref::VoidType(fdecl::VoidRef {}),
10013                fdecl::Availability::Optional,
10014                "fuchsia.examples.Echo3",
10015            ),
10016            // These offers are fine, offers with a source other than parent or void
10017            // can also be optional.
10018            new_offer(
10019                fdecl::Ref::Self_(fdecl::SelfRef {}),
10020                fdecl::Availability::Optional,
10021                "fuchsia.examples.Echo4",
10022            ),
10023            new_offer(
10024                fdecl::Ref::Self_(fdecl::SelfRef {}),
10025                fdecl::Availability::SameAsTarget,
10026                "fuchsia.examples.Echo5",
10027            ),
10028            new_offer(
10029                fdecl::Ref::Framework(fdecl::FrameworkRef {}),
10030                fdecl::Availability::Optional,
10031                "fuchsia.examples.Echo6",
10032            ),
10033            new_offer(
10034                fdecl::Ref::Framework(fdecl::FrameworkRef {}),
10035                fdecl::Availability::SameAsTarget,
10036                "fuchsia.examples.Echo7",
10037            ),
10038            new_offer(
10039                fdecl::Ref::Child(fdecl::ChildRef { name: "source".to_string(), collection: None }),
10040                fdecl::Availability::Optional,
10041                "fuchsia.examples.Echo8",
10042            ),
10043            new_offer(
10044                fdecl::Ref::Child(fdecl::ChildRef { name: "source".to_string(), collection: None }),
10045                fdecl::Availability::SameAsTarget,
10046                "fuchsia.examples.Echo9",
10047            ),
10048            // These offers are also not fine, offers with a source of void must be optional
10049            new_offer(
10050                fdecl::Ref::VoidType(fdecl::VoidRef {}),
10051                fdecl::Availability::Required,
10052                "fuchsia.examples.Echo10",
10053            ),
10054            new_offer(
10055                fdecl::Ref::VoidType(fdecl::VoidRef {}),
10056                fdecl::Availability::SameAsTarget,
10057                "fuchsia.examples.Echo11",
10058            ),
10059        ]);
10060        decl
10061    }
10062
10063    #[test]
10064    fn test_validate_dynamic_offers_empty() {
10065        assert_eq!(
10066            validate_dynamic_offers(vec![], &HashSet::new(), &vec![], &fdecl::Component::default()),
10067            Ok(())
10068        );
10069    }
10070
10071    #[test]
10072    fn test_validate_dynamic_offers_okay() {
10073        assert_eq!(
10074            validate_dynamic_offers(
10075                vec![],
10076                &HashSet::new(),
10077                &vec![
10078                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
10079                        dependency_type: Some(fdecl::DependencyType::Strong),
10080                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef)),
10081                        source_name: Some("thing".to_string()),
10082                        target_name: Some("thing".repeat(26)),
10083                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
10084                            name: "foo".to_string(),
10085                            collection: Some("foo".to_string()),
10086                        })),
10087                        ..Default::default()
10088                    }),
10089                    fdecl::Offer::Service(fdecl::OfferService {
10090                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef)),
10091                        source_name: Some("thang".repeat(26)),
10092                        target_name: Some("thang".repeat(26)),
10093                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
10094                            name: "foo".to_string(),
10095                            collection: Some("foo".to_string()),
10096                        })),
10097                        ..Default::default()
10098                    }),
10099                    fdecl::Offer::Directory(fdecl::OfferDirectory {
10100                        dependency_type: Some(fdecl::DependencyType::Strong),
10101                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef)),
10102                        source_name: Some("thung1".repeat(26)),
10103                        target_name: Some("thung1".repeat(26)),
10104                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
10105                            name: "foo".to_string(),
10106                            collection: Some("foo".to_string()),
10107                        })),
10108                        ..Default::default()
10109                    }),
10110                    fdecl::Offer::Storage(fdecl::OfferStorage {
10111                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef)),
10112                        source_name: Some("thung2".repeat(26)),
10113                        target_name: Some("thung2".repeat(26)),
10114                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
10115                            name: "foo".to_string(),
10116                            collection: Some("foo".to_string()),
10117                        })),
10118                        ..Default::default()
10119                    }),
10120                    fdecl::Offer::Runner(fdecl::OfferRunner {
10121                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef)),
10122                        source_name: Some("thung3".repeat(26)),
10123                        target_name: Some("thung3".repeat(26)),
10124                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
10125                            name: "foo".to_string(),
10126                            collection: Some("foo".to_string()),
10127                        })),
10128                        ..Default::default()
10129                    }),
10130                    fdecl::Offer::Resolver(fdecl::OfferResolver {
10131                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef)),
10132                        source_name: Some("thung4".repeat(26)),
10133                        target_name: Some("thung4".repeat(26)),
10134                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
10135                            name: "foo".to_string(),
10136                            collection: Some("foo".to_string()),
10137                        })),
10138                        ..Default::default()
10139                    }),
10140                ],
10141                &fdecl::Component {
10142                    capabilities: Some(vec![fdecl::Capability::Protocol(fdecl::Protocol {
10143                        name: Some("thing".to_string()),
10144                        source_path: Some("/svc/foo".into()),
10145                        ..Default::default()
10146                    }),]),
10147                    ..Default::default()
10148                }
10149            ),
10150            Ok(())
10151        );
10152    }
10153
10154    #[test]
10155    fn test_validate_dynamic_offers_valid_service_aggregation() {
10156        assert_eq!(
10157            validate_dynamic_offers(
10158                vec![],
10159                &HashSet::new(),
10160                &vec![
10161                    fdecl::Offer::Service(fdecl::OfferService {
10162                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
10163                            name: "child_a".to_string(),
10164                            collection: None
10165                        })),
10166                        source_name: Some("fuchsia.logger.Log".to_string()),
10167                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
10168                            name: "child_c".to_string(),
10169                            collection: None,
10170                        })),
10171                        target_name: Some("fuchsia.logger.Log".to_string()),
10172                        source_instance_filter: Some(vec!["default".to_string()]),
10173                        ..Default::default()
10174                    }),
10175                    fdecl::Offer::Service(fdecl::OfferService {
10176                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
10177                            name: "child_b".to_string(),
10178                            collection: None
10179                        })),
10180                        source_name: Some("fuchsia.logger.Log".to_string()),
10181                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
10182                            name: "child_c".to_string(),
10183                            collection: None,
10184                        })),
10185                        target_name: Some("fuchsia.logger.Log".to_string()),
10186                        source_instance_filter: Some(vec!["a_different_default".to_string()]),
10187                        ..Default::default()
10188                    })
10189                ],
10190                &fdecl::Component {
10191                    children: Some(vec![
10192                        fdecl::Child {
10193                            name: Some("child_a".to_string()),
10194                            url: Some(
10195                                "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
10196                                    .to_string()
10197                            ),
10198                            startup: Some(fdecl::StartupMode::Lazy),
10199                            on_terminate: None,
10200                            environment: None,
10201                            ..Default::default()
10202                        },
10203                        fdecl::Child {
10204                            name: Some("child_b".to_string()),
10205                            url: Some(
10206                                "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
10207                                    .to_string()
10208                            ),
10209                            startup: Some(fdecl::StartupMode::Lazy),
10210                            on_terminate: None,
10211                            environment: None,
10212                            ..Default::default()
10213                        },
10214                        fdecl::Child {
10215                            name: Some("child_c".to_string()),
10216                            url: Some(
10217                                "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
10218                                    .to_string()
10219                            ),
10220                            startup: Some(fdecl::StartupMode::Lazy),
10221                            on_terminate: None,
10222                            environment: None,
10223                            ..Default::default()
10224                        },
10225                    ]),
10226                    ..Default::default()
10227                }
10228            ),
10229            Ok(())
10230        );
10231    }
10232
10233    #[test]
10234    fn test_validate_dynamic_service_aggregation_missing_filter() {
10235        assert_eq!(
10236            validate_dynamic_offers(
10237                vec![],
10238                &HashSet::new(),
10239                &vec![
10240                    fdecl::Offer::Service(fdecl::OfferService {
10241                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
10242                            name: "child_a".to_string(),
10243                            collection: None
10244                        })),
10245                        source_name: Some("fuchsia.logger.Log".to_string()),
10246                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
10247                            name: "child_c".to_string(),
10248                            collection: None,
10249                        })),
10250                        target_name: Some("fuchsia.logger.Log".to_string()),
10251                        source_instance_filter: Some(vec!["default".to_string()]),
10252                        ..Default::default()
10253                    }),
10254                    fdecl::Offer::Service(fdecl::OfferService {
10255                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
10256                            name: "child_b".to_string(),
10257                            collection: None
10258                        })),
10259                        source_name: Some("fuchsia.logger.Log".to_string()),
10260                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
10261                            name: "child_c".to_string(),
10262                            collection: None,
10263                        })),
10264                        target_name: Some("fuchsia.logger.Log".to_string()),
10265                        source_instance_filter: None,
10266                        ..Default::default()
10267                    }),
10268                ],
10269                &fdecl::Component {
10270                    children: Some(vec![
10271                        fdecl::Child {
10272                            name: Some("child_a".to_string()),
10273                            url: Some(
10274                                "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
10275                                    .to_string()
10276                            ),
10277                            startup: Some(fdecl::StartupMode::Lazy),
10278                            ..Default::default()
10279                        },
10280                        fdecl::Child {
10281                            name: Some("child_b".to_string()),
10282                            url: Some(
10283                                "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
10284                                    .to_string()
10285                            ),
10286                            startup: Some(fdecl::StartupMode::Lazy),
10287                            ..Default::default()
10288                        },
10289                        fdecl::Child {
10290                            name: Some("child_c".to_string()),
10291                            url: Some(
10292                                "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
10293                                    .to_string()
10294                            ),
10295                            startup: Some(fdecl::StartupMode::Lazy),
10296                            ..Default::default()
10297                        },
10298                    ]),
10299                    ..Default::default()
10300                },
10301            ),
10302            Err(ErrorList::new(vec![Error::invalid_aggregate_offer(
10303                "source_instance_filter must be set for dynamic aggregate service offers"
10304            ),]))
10305        );
10306    }
10307
10308    #[test]
10309    fn test_validate_dynamic_offers_omit_target() {
10310        assert_eq!(
10311            validate_dynamic_offers(
10312                vec![],
10313                &HashSet::new(),
10314                &vec![
10315                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
10316                        dependency_type: Some(fdecl::DependencyType::Strong),
10317                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef)),
10318                        source_name: Some("thing".to_string()),
10319                        target_name: Some("thing".to_string()),
10320                        ..Default::default()
10321                    }),
10322                    fdecl::Offer::Service(fdecl::OfferService {
10323                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef)),
10324                        source_name: Some("thang".to_string()),
10325                        target_name: Some("thang".to_string()),
10326                        ..Default::default()
10327                    }),
10328                    fdecl::Offer::Directory(fdecl::OfferDirectory {
10329                        dependency_type: Some(fdecl::DependencyType::Strong),
10330                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef)),
10331                        source_name: Some("thung1".to_string()),
10332                        target_name: Some("thung1".to_string()),
10333                        ..Default::default()
10334                    }),
10335                    fdecl::Offer::Storage(fdecl::OfferStorage {
10336                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef)),
10337                        source_name: Some("thung2".to_string()),
10338                        target_name: Some("thung2".to_string()),
10339                        ..Default::default()
10340                    }),
10341                    fdecl::Offer::Runner(fdecl::OfferRunner {
10342                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef)),
10343                        source_name: Some("thung3".to_string()),
10344                        target_name: Some("thung3".to_string()),
10345                        ..Default::default()
10346                    }),
10347                    fdecl::Offer::Resolver(fdecl::OfferResolver {
10348                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef)),
10349                        source_name: Some("thung4".to_string()),
10350                        target_name: Some("thung4".to_string()),
10351                        ..Default::default()
10352                    }),
10353                ],
10354                &fdecl::Component {
10355                    capabilities: Some(vec![fdecl::Capability::Protocol(fdecl::Protocol {
10356                        name: Some("thing".to_string()),
10357                        source_path: Some("/svc/foo".into()),
10358                        ..Default::default()
10359                    }),]),
10360                    ..Default::default()
10361                }
10362            ),
10363            Err(ErrorList::new(vec![
10364                Error::missing_field(DeclType::OfferProtocol, "target"),
10365                Error::missing_field(DeclType::OfferService, "target"),
10366                Error::missing_field(DeclType::OfferDirectory, "target"),
10367                Error::missing_field(DeclType::OfferStorage, "target"),
10368                Error::missing_field(DeclType::OfferRunner, "target"),
10369                Error::missing_field(DeclType::OfferResolver, "target"),
10370            ]))
10371        );
10372    }
10373
10374    #[test]
10375    fn test_validate_dynamic_offers_collection_collision() {
10376        assert_eq!(
10377            validate_dynamic_offers(
10378                vec![],
10379                &HashSet::new(),
10380                &vec![fdecl::Offer::Protocol(fdecl::OfferProtocol {
10381                    dependency_type: Some(fdecl::DependencyType::Strong),
10382                    source: Some(fdecl::Ref::Parent(fdecl::ParentRef)),
10383                    source_name: Some("thing".to_string()),
10384                    target_name: Some("thing".to_string()),
10385                    target: Some(fdecl::Ref::Child(fdecl::ChildRef {
10386                        name: "child".to_string(),
10387                        collection: Some("coll".to_string()),
10388                    })),
10389                    ..Default::default()
10390                }),],
10391                &fdecl::Component {
10392                    offers: Some(vec![fdecl::Offer::Protocol(fdecl::OfferProtocol {
10393                        dependency_type: Some(fdecl::DependencyType::Strong),
10394                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef)),
10395                        source_name: Some("thing".to_string()),
10396                        target_name: Some("thing".to_string()),
10397                        target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
10398                            name: "coll".into()
10399                        })),
10400                        ..Default::default()
10401                    }),]),
10402                    collections: Some(vec![fdecl::Collection {
10403                        name: Some("coll".to_string()),
10404                        durability: Some(fdecl::Durability::Transient),
10405                        ..Default::default()
10406                    },]),
10407                    ..Default::default()
10408                }
10409            ),
10410            Err(ErrorList::new(vec![Error::duplicate_field(
10411                DeclType::OfferProtocol,
10412                "target_name",
10413                "thing"
10414            ),]))
10415        );
10416    }
10417
10418    #[test]
10419    fn test_validate_dynamic_offers_cycle_collection_to_static_child() {
10420        assert_eq!(
10421            validate_dynamic_offers(
10422                vec![("dyn", "coll")],
10423                &HashSet::new(),
10424                &vec![fdecl::Offer::Protocol(fdecl::OfferProtocol {
10425                    source_name: Some("bar".to_string()),
10426                    target_name: Some("bar".to_string()),
10427                    source: Some(fdecl::Ref::Child(fdecl::ChildRef {
10428                        name: "static_child".into(),
10429                        collection: None,
10430                    })),
10431                    target: Some(fdecl::Ref::Child(fdecl::ChildRef {
10432                        name: "dyn".to_string(),
10433                        collection: Some("coll".to_string()),
10434                    })),
10435                    dependency_type: Some(fdecl::DependencyType::Strong),
10436                    ..Default::default()
10437                }),],
10438                &fdecl::Component {
10439                    offers: Some(vec![fdecl::Offer::Service(fdecl::OfferService {
10440                        source_name: Some("foo".to_string()),
10441                        target_name: Some("foo".to_string()),
10442                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
10443                            name: "coll".into(),
10444                        })),
10445                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
10446                            name: "static_child".into(),
10447                            collection: None,
10448                        })),
10449                        ..Default::default()
10450                    })]),
10451                    children: Some(vec![fdecl::Child {
10452                        name: Some("static_child".into()),
10453                        url: Some("url#child.cm".into()),
10454                        startup: Some(fdecl::StartupMode::Lazy),
10455                        ..Default::default()
10456                    }]),
10457                    collections: Some(vec![fdecl::Collection {
10458                        name: Some("coll".into()),
10459                        durability: Some(fdecl::Durability::Transient),
10460                        ..Default::default()
10461                    }]),
10462                    ..Default::default()
10463                }
10464            ),
10465            Err(ErrorList::new(vec![Error::dependency_cycle(
10466                "{{child coll:dyn -> collection coll -> child static_child -> child coll:dyn}}"
10467            )]))
10468        );
10469    }
10470
10471    #[test]
10472    fn test_validate_dynamic_offers_cycle_collection_to_dynamic_child() {
10473        assert_eq!(
10474            validate_dynamic_offers(
10475                vec![("dyn", "coll1"), ("dyn", "coll2")],
10476                &HashSet::new(),
10477                &vec![
10478                    fdecl::Offer::Service(fdecl::OfferService {
10479                        source_name: Some("foo".to_string()),
10480                        target_name: Some("foo".to_string()),
10481                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
10482                            name: "coll2".into(),
10483                        })),
10484                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
10485                            name: "dyn".into(),
10486                            collection: Some("coll1".into()),
10487                        })),
10488                        ..Default::default()
10489                    }),
10490                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
10491                        source_name: Some("bar".to_string()),
10492                        target_name: Some("bar".to_string()),
10493                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
10494                            name: "dyn".into(),
10495                            collection: Some("coll1".into()),
10496                        })),
10497                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
10498                            name: "dyn".to_string(),
10499                            collection: Some("coll2".to_string()),
10500                        })),
10501                        dependency_type: Some(fdecl::DependencyType::Strong),
10502                        ..Default::default()
10503                    }),
10504                ],
10505                &fdecl::Component {
10506                    collections: Some(vec![
10507                        fdecl::Collection {
10508                            name: Some("coll1".into()),
10509                            durability: Some(fdecl::Durability::Transient),
10510                            ..Default::default()
10511                        },
10512                        fdecl::Collection {
10513                            name: Some("coll2".into()),
10514                            durability: Some(fdecl::Durability::Transient),
10515                            ..Default::default()
10516                        },
10517                    ]),
10518                    ..Default::default()
10519                }
10520            ),
10521            Err(ErrorList::new(vec![Error::dependency_cycle(
10522                "{{child coll1:dyn -> child coll2:dyn -> collection coll2 -> child coll1:dyn}}"
10523            )]))
10524        );
10525    }
10526
10527    #[test]
10528    fn test_validate_dynamic_offers_cycle_collection_to_dynamic_child_between_new_and_existing() {
10529        assert_eq!(
10530            validate_dynamic_offers(
10531                vec![("dyn", "coll1"), ("dyn", "coll2")],
10532                &HashSet::from([(
10533                    DependencyNode::Child("dyn".into(), Some("coll1".into())),
10534                    DependencyNode::Child("dyn".into(), Some("coll2".into())),
10535                )]),
10536                &vec![fdecl::Offer::Service(fdecl::OfferService {
10537                    source_name: Some("foo".to_string()),
10538                    target_name: Some("foo".to_string()),
10539                    source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
10540                        name: "coll2".into(),
10541                    })),
10542                    target: Some(fdecl::Ref::Child(fdecl::ChildRef {
10543                        name: "dyn".into(),
10544                        collection: Some("coll1".into()),
10545                    })),
10546                    ..Default::default()
10547                }),],
10548                &fdecl::Component {
10549                    collections: Some(vec![
10550                        fdecl::Collection {
10551                            name: Some("coll1".into()),
10552                            durability: Some(fdecl::Durability::Transient),
10553                            ..Default::default()
10554                        },
10555                        fdecl::Collection {
10556                            name: Some("coll2".into()),
10557                            durability: Some(fdecl::Durability::Transient),
10558                            ..Default::default()
10559                        },
10560                    ]),
10561                    ..Default::default()
10562                }
10563            ),
10564            Err(ErrorList::new(vec![Error::dependency_cycle(
10565                "{{child coll1:dyn -> child coll2:dyn -> collection coll2 -> child coll1:dyn}}"
10566            )]))
10567        );
10568    }
10569
10570    #[test]
10571    fn test_validate_dynamic_offers_cycle_collection_to_collection() {
10572        assert_eq!(
10573            validate_dynamic_offers(
10574                vec![("dyn", "coll1"), ("dyn", "coll2")],
10575                &HashSet::new(),
10576                &vec![fdecl::Offer::Protocol(fdecl::OfferProtocol {
10577                    source_name: Some("bar".to_string()),
10578                    target_name: Some("bar".to_string()),
10579                    source: Some(fdecl::Ref::Child(fdecl::ChildRef {
10580                        name: "dyn".into(),
10581                        collection: Some("coll2".parse().unwrap()),
10582                    })),
10583                    target: Some(fdecl::Ref::Child(fdecl::ChildRef {
10584                        name: "dyn".into(),
10585                        collection: Some("coll1".parse().unwrap()),
10586                    })),
10587                    dependency_type: Some(fdecl::DependencyType::Strong),
10588                    ..Default::default()
10589                }),],
10590                &fdecl::Component {
10591                    offers: Some(vec![fdecl::Offer::Service(fdecl::OfferService {
10592                        source_name: Some("foo".to_string()),
10593                        target_name: Some("foo".to_string()),
10594                        source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
10595                            name: "coll1".into(),
10596                        })),
10597                        target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
10598                            name: "coll2".into(),
10599                        })),
10600                        ..Default::default()
10601                    })]),
10602                    collections: Some(vec![
10603                        fdecl::Collection {
10604                            name: Some("coll1".into()),
10605                            durability: Some(fdecl::Durability::Transient),
10606                            ..Default::default()
10607                        },
10608                        fdecl::Collection {
10609                            name: Some("coll2".into()),
10610                            durability: Some(fdecl::Durability::Transient),
10611                            ..Default::default()
10612                        },
10613                    ]),
10614                    ..Default::default()
10615                }
10616            ),
10617            Err(ErrorList::new(vec![Error::dependency_cycle(
10618                "{{child coll1:dyn -> collection coll1 -> child coll2:dyn -> child coll1:dyn}}",
10619            )]))
10620        );
10621    }
10622
10623    #[test]
10624    fn test_validate_dynamic_child() {
10625        assert_eq!(
10626            Ok(()),
10627            validate_dynamic_child(&fdecl::Child {
10628                name: Some("a".repeat(MAX_LONG_NAME_LENGTH).to_string()),
10629                url: Some("test:///child".to_string()),
10630                startup: Some(fdecl::StartupMode::Lazy),
10631                on_terminate: None,
10632                environment: None,
10633                ..Default::default()
10634            })
10635        );
10636    }
10637
10638    #[test]
10639    fn test_validate_dynamic_child_environment_is_invalid() {
10640        assert_eq!(
10641            validate_dynamic_child(&fdecl::Child {
10642                name: Some("a".repeat(MAX_LONG_NAME_LENGTH).to_string()),
10643                url: Some("test:///child".to_string()),
10644                startup: Some(fdecl::StartupMode::Lazy),
10645                on_terminate: None,
10646                environment: Some("env".to_string()),
10647                ..Default::default()
10648            }),
10649            Err(ErrorList::new(vec![Error::DynamicChildWithEnvironment]))
10650        );
10651    }
10652
10653    #[test]
10654    fn test_validate_dynamic_offers_missing_stuff() {
10655        assert_eq!(
10656            validate_dynamic_offers(
10657                vec![],
10658                &HashSet::new(),
10659                &vec![
10660                    fdecl::Offer::Protocol(fdecl::OfferProtocol::default()),
10661                    fdecl::Offer::Service(fdecl::OfferService::default()),
10662                    fdecl::Offer::Directory(fdecl::OfferDirectory::default()),
10663                    fdecl::Offer::Storage(fdecl::OfferStorage::default()),
10664                    fdecl::Offer::Runner(fdecl::OfferRunner::default()),
10665                    fdecl::Offer::Resolver(fdecl::OfferResolver::default()),
10666                ],
10667                &fdecl::Component::default()
10668            ),
10669            Err(ErrorList::new(vec![
10670                Error::missing_field(DeclType::OfferProtocol, "source"),
10671                Error::missing_field(DeclType::OfferProtocol, "source_name"),
10672                Error::missing_field(DeclType::OfferProtocol, "target"),
10673                Error::missing_field(DeclType::OfferProtocol, "target_name"),
10674                Error::missing_field(DeclType::OfferProtocol, "dependency_type"),
10675                Error::missing_field(DeclType::OfferService, "source"),
10676                Error::missing_field(DeclType::OfferService, "source_name"),
10677                Error::missing_field(DeclType::OfferService, "target"),
10678                Error::missing_field(DeclType::OfferService, "target_name"),
10679                Error::missing_field(DeclType::OfferDirectory, "source"),
10680                Error::missing_field(DeclType::OfferDirectory, "source_name"),
10681                Error::missing_field(DeclType::OfferDirectory, "target"),
10682                Error::missing_field(DeclType::OfferDirectory, "target_name"),
10683                Error::missing_field(DeclType::OfferDirectory, "dependency_type"),
10684                Error::missing_field(DeclType::OfferStorage, "source"),
10685                Error::missing_field(DeclType::OfferStorage, "source_name"),
10686                Error::missing_field(DeclType::OfferStorage, "target"),
10687                Error::missing_field(DeclType::OfferStorage, "target_name"),
10688                Error::missing_field(DeclType::OfferRunner, "source"),
10689                Error::missing_field(DeclType::OfferRunner, "source_name"),
10690                Error::missing_field(DeclType::OfferRunner, "target"),
10691                Error::missing_field(DeclType::OfferRunner, "target_name"),
10692                Error::missing_field(DeclType::OfferResolver, "source"),
10693                Error::missing_field(DeclType::OfferResolver, "source_name"),
10694                Error::missing_field(DeclType::OfferResolver, "target"),
10695                Error::missing_field(DeclType::OfferResolver, "target_name"),
10696            ]))
10697        );
10698    }
10699
10700    test_dependency! {
10701        (test_validate_offers_protocol_dependency_cycle) => {
10702            ty = fdecl::Offer::Protocol,
10703            offer_decl = fdecl::OfferProtocol {
10704                source: None,  // Filled by macro
10705                target: None,  // Filled by macro
10706                source_name: Some(format!("thing")),
10707                target_name: Some(format!("thing")),
10708                dependency_type: Some(fdecl::DependencyType::Strong),
10709                ..Default::default()
10710            },
10711        },
10712        (test_validate_offers_directory_dependency_cycle) => {
10713            ty = fdecl::Offer::Directory,
10714            offer_decl = fdecl::OfferDirectory {
10715                source: None,  // Filled by macro
10716                target: None,  // Filled by macro
10717                source_name: Some(format!("thing")),
10718                target_name: Some(format!("thing")),
10719                rights: Some(fio::Operations::CONNECT),
10720                subdir: None,
10721                dependency_type: Some(fdecl::DependencyType::Strong),
10722                ..Default::default()
10723            },
10724        },
10725        (test_validate_offers_service_dependency_cycle) => {
10726            ty = fdecl::Offer::Service,
10727            offer_decl = fdecl::OfferService {
10728                source: None,  // Filled by macro
10729                target: None,  // Filled by macro
10730                source_name: Some(format!("thing")),
10731                target_name: Some(format!("thing")),
10732                ..Default::default()
10733            },
10734        },
10735        (test_validate_offers_runner_dependency_cycle) => {
10736            ty = fdecl::Offer::Runner,
10737            offer_decl = fdecl::OfferRunner {
10738                source: None,  // Filled by macro
10739                target: None,  // Filled by macro
10740                source_name: Some(format!("thing")),
10741                target_name: Some(format!("thing")),
10742                ..Default::default()
10743            },
10744        },
10745        (test_validate_offers_resolver_dependency_cycle) => {
10746            ty = fdecl::Offer::Resolver,
10747            offer_decl = fdecl::OfferResolver {
10748                source: None,  // Filled by macro
10749                target: None,  // Filled by macro
10750                source_name: Some(format!("thing")),
10751                target_name: Some(format!("thing")),
10752                ..Default::default()
10753            },
10754        },
10755    }
10756    test_weak_dependency! {
10757        (test_validate_offers_protocol_weak_dependency_cycle) => {
10758            ty = fdecl::Offer::Protocol,
10759            offer_decl = fdecl::OfferProtocol {
10760                source: None,  // Filled by macro
10761                target: None,  // Filled by macro
10762                source_name: Some(format!("thing")),
10763                target_name: Some(format!("thing")),
10764                dependency_type: None, // Filled by macro
10765                ..Default::default()
10766            },
10767        },
10768        (test_validate_offers_directory_weak_dependency_cycle) => {
10769            ty = fdecl::Offer::Directory,
10770            offer_decl = fdecl::OfferDirectory {
10771                source: None,  // Filled by macro
10772                target: None,  // Filled by macro
10773                source_name: Some(format!("thing")),
10774                target_name: Some(format!("thing")),
10775                rights: Some(fio::Operations::CONNECT),
10776                subdir: None,
10777                dependency_type: None,  // Filled by macro
10778                ..Default::default()
10779            },
10780        },
10781        (test_validate_offers_service_weak_dependency_cycle) => {
10782            ty = fdecl::Offer::Service,
10783            offer_decl = fdecl::OfferService {
10784                source: None,  // Filled by macro
10785                target: None,  // Filled by macro
10786                source_name: Some(format!("thing")),
10787                target_name: Some(format!("thing")),
10788                dependency_type: None, // Filled by macro
10789                ..Default::default()
10790            },
10791        },
10792    }
10793}