cml/
translate.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::error::Error;
6use crate::features::{Feature, FeatureSet};
7use crate::types::child::Child;
8use crate::types::collection::Collection;
9use crate::types::document::Document;
10use crate::types::environment::{
11    DebugRegistration, Environment, EnvironmentExtends, EnvironmentRef, RunnerRegistration,
12};
13use crate::types::expose::{Expose, ExposeFromRef, ExposeToRef};
14use crate::types::offer::{Offer, OfferFromRef, OfferToRef, TargetAvailability};
15use crate::types::right::RightsClause;
16use crate::types::r#use::{Use, UseFromRef};
17use crate::validate::CapabilityRequirements;
18use crate::{
19    AnyRef, AsClause, Availability, Capability, CapabilityClause, ConfigKey, ConfigNestedValueType,
20    ConfigRuntimeSource, ConfigType, ConfigValueType, DictionaryRef, EventScope, FromClause,
21    OneOrMany, Path, PathClause, Program, ResolverRegistration, RootDictionaryRef,
22    SourceAvailability, offer_to_all_would_duplicate, validate,
23};
24use cm_rust::NativeIntoFidl;
25use cm_types::{self as cm, BorrowedName, Name};
26use directed_graph::DirectedGraph;
27use indexmap::IndexMap;
28use itertools::Itertools;
29use serde_json::{Map, Value};
30use sha2::{Digest, Sha256};
31use std::collections::{BTreeMap, BTreeSet};
32use std::convert::{Into, TryInto};
33use std::path::PathBuf;
34use {fidl_fuchsia_component_decl as fdecl, fidl_fuchsia_data as fdata, fidl_fuchsia_io as fio};
35
36/// Options for CML compilation. Uses the builder pattern.
37#[derive(Default, Clone)]
38pub struct CompileOptions<'a> {
39    file: Option<PathBuf>,
40    config_package_path: Option<String>,
41    features: Option<&'a FeatureSet>,
42    capability_requirements: CapabilityRequirements<'a>,
43}
44
45impl<'a> CompileOptions<'a> {
46    pub fn new() -> Self {
47        Default::default()
48    }
49
50    /// The path to the CML file, if applicable. Used for error reporting.
51    pub fn file(mut self, file: &std::path::Path) -> CompileOptions<'a> {
52        self.file = Some(file.to_path_buf());
53        self
54    }
55
56    /// The path within the component's package at which to find config value files.
57    pub fn config_package_path(mut self, config_package_path: &str) -> CompileOptions<'a> {
58        self.config_package_path = Some(config_package_path.to_string());
59        self
60    }
61
62    /// Which additional features are enabled. Defaults to none.
63    pub fn features(mut self, features: &'a FeatureSet) -> CompileOptions<'a> {
64        self.features = Some(features);
65        self
66    }
67
68    /// Require that the component must use or offer particular protocols. Defaults to no
69    /// requirements.
70    pub fn protocol_requirements(
71        mut self,
72        protocol_requirements: CapabilityRequirements<'a>,
73    ) -> CompileOptions<'a> {
74        self.capability_requirements = protocol_requirements;
75        self
76    }
77}
78
79/// Compiles the [Document] into a FIDL [fdecl::Component].
80/// `options` is a builder used to provide additional options, such as file path for debugging
81/// purposes.
82///
83/// Note: This function ignores the `include` section of the document. It is
84/// assumed that those entries were already processed.
85pub fn compile(
86    document: &Document,
87    options: CompileOptions<'_>,
88) -> Result<fdecl::Component, Error> {
89    validate::validate_cml(
90        &document,
91        options.file.as_ref().map(PathBuf::as_path),
92        options.features.unwrap_or(&FeatureSet::empty()),
93        &options.capability_requirements,
94    )?;
95
96    let all_capability_names: BTreeSet<&BorrowedName> =
97        document.all_capability_names().into_iter().collect();
98    let all_children = document.all_children_names().into_iter().collect();
99    let all_collections = document.all_collection_names().into_iter().collect();
100    let component = fdecl::Component {
101        program: document.program.as_ref().map(translate_program).transpose()?,
102        uses: document
103            .r#use
104            .as_ref()
105            .map(|u| {
106                translate_use(&options, u, &all_capability_names, &all_children, &all_collections)
107            })
108            .transpose()?,
109        exposes: document
110            .expose
111            .as_ref()
112            .map(|e| {
113                translate_expose(
114                    &options,
115                    e,
116                    &all_capability_names,
117                    &all_collections,
118                    &all_children,
119                )
120            })
121            .transpose()?,
122        offers: document
123            .offer
124            .as_ref()
125            .map(|offer| {
126                translate_offer(
127                    &options,
128                    offer,
129                    &all_capability_names,
130                    &all_children,
131                    &all_collections,
132                )
133            })
134            .transpose()?,
135        capabilities: document
136            .capabilities
137            .as_ref()
138            .map(|c| translate_capabilities(&options, c, false))
139            .transpose()?,
140        children: document.children.as_ref().map(translate_children),
141        collections: document.collections.as_ref().map(translate_collections),
142        environments: document
143            .environments
144            .as_ref()
145            .map(|env| translate_environments(&options, env, &all_capability_names))
146            .transpose()?,
147        facets: document.facets.clone().map(dictionary_from_nested_map).transpose()?,
148        config: translate_config(&document.config, &document.r#use, &options.config_package_path)?,
149        ..Default::default()
150    };
151
152    let mut deps = DirectedGraph::new();
153    cm_fidl_validator::validate(&component, &mut deps).map_err(Error::fidl_validator)?;
154
155    Ok(component)
156}
157
158// Converts a Map<String, serde_json::Value> to a fuchsia Dictionary.
159fn dictionary_from_map(in_obj: Map<String, Value>) -> Result<fdata::Dictionary, Error> {
160    let mut entries = vec![];
161    for (key, v) in in_obj {
162        let value = value_to_dictionary_value(v)?;
163        entries.push(fdata::DictionaryEntry { key, value });
164    }
165    Ok(fdata::Dictionary { entries: Some(entries), ..Default::default() })
166}
167
168// Converts a serde_json::Value into a fuchsia DictionaryValue.
169fn value_to_dictionary_value(value: Value) -> Result<Option<Box<fdata::DictionaryValue>>, Error> {
170    match value {
171        Value::Null => Ok(None),
172        Value::String(s) => Ok(Some(Box::new(fdata::DictionaryValue::Str(s)))),
173        Value::Array(arr) => {
174            if arr.iter().all(Value::is_string) {
175                let strs =
176                    arr.into_iter().map(|v| v.as_str().unwrap().to_owned()).collect::<Vec<_>>();
177                Ok(Some(Box::new(fdata::DictionaryValue::StrVec(strs))))
178            } else if arr.iter().all(Value::is_object) {
179                let objs = arr
180                    .into_iter()
181                    .map(|v| v.as_object().unwrap().clone())
182                    .map(|v| dictionary_from_nested_map(v.into_iter().collect()))
183                    .collect::<Result<Vec<_>, _>>()?;
184                Ok(Some(Box::new(fdata::DictionaryValue::ObjVec(objs))))
185            } else {
186                Err(Error::validate(
187                    "Values of an array must either exclusively strings or exclusively objects",
188                ))
189            }
190        }
191        other => Err(Error::validate(format!(
192            "Value must be string, list of strings, or list of objects: {:?}",
193            other
194        ))),
195    }
196}
197
198/// Converts a [`serde_json::Map<String, serde_json::Value>`] to a [`fuchsia.data.Dictionary`].
199///
200/// The JSON object is converted as follows:
201///
202/// * Convert all non-string and string values into DictionaryValue::str.
203/// * Flatten nested objects into top-level keys delimited by ".".
204/// * Convert array of discrete values into  array of DictionaryValue::str_vec.
205/// * Convert array of objects into array of DictionaryValue::obj_vec.
206///
207/// Values may be null, strings, arrays of strings, arrays of objects, or objects.
208///
209/// # Example
210///
211/// ```json
212/// {
213///   "binary": "bin/app",
214///   "lifecycle": {
215///     "stop_event": "notify",
216///     "nested": {
217///       "foo": "bar"
218///     }
219///   }
220/// }
221/// ```
222///
223/// is flattened to:
224///
225/// ```json
226/// {
227///   "binary": "bin/app",
228///   "lifecycle.stop_event": "notify",
229///   "lifecycle.nested.foo": "bar"
230/// }
231/// ```
232fn dictionary_from_nested_map(map: IndexMap<String, Value>) -> Result<fdata::Dictionary, Error> {
233    fn key_value_to_entries(
234        key: String,
235        value: Value,
236    ) -> Result<Vec<fdata::DictionaryEntry>, Error> {
237        if let Value::Object(map) = value {
238            let entries = map
239                .into_iter()
240                .map(|(k, v)| key_value_to_entries([key.clone(), ".".to_string(), k].concat(), v))
241                .collect::<Result<Vec<_>, _>>()?
242                .into_iter()
243                .flatten()
244                .collect();
245            return Ok(entries);
246        }
247
248        let entry_value = value_to_dictionary_value(value)?;
249        Ok(vec![fdata::DictionaryEntry { key, value: entry_value }])
250    }
251
252    let entries = map
253        .into_iter()
254        .map(|(k, v)| key_value_to_entries(k, v))
255        .collect::<Result<Vec<_>, _>>()?
256        .into_iter()
257        .flatten()
258        .collect();
259    Ok(fdata::Dictionary { entries: Some(entries), ..Default::default() })
260}
261
262/// Translates a [`Program`] to a [`fuchsia.component.decl/Program`].
263fn translate_program(program: &Program) -> Result<fdecl::Program, Error> {
264    Ok(fdecl::Program {
265        runner: program.runner.as_ref().map(|r| r.to_string()),
266        info: Some(dictionary_from_nested_map(program.info.clone())?),
267        ..Default::default()
268    })
269}
270
271/// `use` rules consume a single capability from one source (parent|framework).
272fn translate_use(
273    options: &CompileOptions<'_>,
274    use_in: &Vec<Use>,
275    all_capability_names: &BTreeSet<&BorrowedName>,
276    all_children: &BTreeSet<&BorrowedName>,
277    all_collections: &BTreeSet<&BorrowedName>,
278) -> Result<Vec<fdecl::Use>, Error> {
279    let mut out_uses = vec![];
280    for use_ in use_in {
281        if let Some(n) = use_.service() {
282            let (source, source_dictionary) = extract_use_source(
283                options,
284                use_,
285                all_capability_names,
286                all_children,
287                Some(all_collections),
288            )?;
289            let target_paths =
290                all_target_use_paths(use_, use_).ok_or_else(|| Error::internal("no capability"))?;
291            let source_names = n.into_iter();
292            let availability = extract_use_availability(use_)?;
293            for (source_name, target_path) in source_names.into_iter().zip(target_paths.into_iter())
294            {
295                out_uses.push(fdecl::Use::Service(fdecl::UseService {
296                    source: Some(source.clone()),
297                    source_name: Some(source_name.to_string()),
298                    source_dictionary: source_dictionary.clone(),
299                    target_path: Some(target_path.to_string()),
300                    dependency_type: Some(
301                        use_.dependency.clone().unwrap_or(cm::DependencyType::Strong).into(),
302                    ),
303                    availability: Some(availability),
304                    ..Default::default()
305                }));
306            }
307        } else if let Some(n) = use_.protocol() {
308            let (source, source_dictionary) =
309                extract_use_source(options, use_, all_capability_names, all_children, None)?;
310            let availability = extract_use_availability(use_)?;
311            if use_.numbered_handle.is_some() {
312                let OneOrMany::One(source_name) = n else {
313                    panic!("numbered_handle: multiple source_name");
314                };
315                out_uses.push(fdecl::Use::Protocol(fdecl::UseProtocol {
316                    source: Some(source.clone()),
317                    source_name: Some(source_name.to_string()),
318                    source_dictionary: source_dictionary.clone(),
319                    target_path: None,
320                    numbered_handle: use_.numbered_handle.map(Into::into),
321                    dependency_type: Some(
322                        use_.dependency.clone().unwrap_or(cm::DependencyType::Strong).into(),
323                    ),
324                    availability: Some(availability),
325                    ..Default::default()
326                }));
327                continue;
328            }
329            let source_names = n.into_iter();
330            let target_paths =
331                all_target_use_paths(use_, use_).ok_or_else(|| Error::internal("no capability"))?;
332            for (source_name, target_path) in source_names.into_iter().zip(target_paths.into_iter())
333            {
334                out_uses.push(fdecl::Use::Protocol(fdecl::UseProtocol {
335                    source: Some(source.clone()),
336                    source_name: Some(source_name.to_string()),
337                    source_dictionary: source_dictionary.clone(),
338                    target_path: Some(target_path.into()),
339                    numbered_handle: use_.numbered_handle.map(Into::into),
340                    dependency_type: Some(
341                        use_.dependency.clone().unwrap_or(cm::DependencyType::Strong).into(),
342                    ),
343                    availability: Some(availability),
344                    ..Default::default()
345                }));
346            }
347        } else if let Some(n) = &use_.directory {
348            let (source, source_dictionary) =
349                extract_use_source(options, use_, all_capability_names, all_children, None)?;
350            let target_path = one_target_use_path(use_, use_)?;
351            let rights = extract_required_rights(use_, "use")?;
352            let subdir = extract_use_subdir(use_);
353            let availability = extract_use_availability(use_)?;
354            out_uses.push(fdecl::Use::Directory(fdecl::UseDirectory {
355                source: Some(source),
356                source_name: Some(n.clone().into()),
357                source_dictionary,
358                target_path: Some(target_path.into()),
359                rights: Some(rights),
360                subdir: subdir.map(|s| s.into()),
361                dependency_type: Some(
362                    use_.dependency.clone().unwrap_or(cm::DependencyType::Strong).into(),
363                ),
364                availability: Some(availability),
365                ..Default::default()
366            }));
367        } else if let Some(n) = &use_.storage {
368            let target_path = one_target_use_path(use_, use_)?;
369            let availability = extract_use_availability(use_)?;
370            out_uses.push(fdecl::Use::Storage(fdecl::UseStorage {
371                source_name: Some(n.clone().into()),
372                target_path: Some(target_path.into()),
373                availability: Some(availability),
374                ..Default::default()
375            }));
376        } else if let Some(names) = &use_.event_stream {
377            let source_names: Vec<String> =
378                annotate_type::<Vec<cm_types::Name>>(names.clone().into())
379                    .iter()
380                    .map(|name| name.to_string())
381                    .collect();
382            let availability = extract_use_availability(use_)?;
383            for name in source_names {
384                let scopes = match use_.scope.clone() {
385                    Some(value) => Some(annotate_type::<Vec<EventScope>>(value.into())),
386                    None => None,
387                };
388                let internal_error = format!(
389                    "Internal error in all_target_use_paths when translating an EventStream. \
390                    Please file a bug."
391                );
392                let (source, _source_dictionary) =
393                    extract_use_source(options, use_, all_capability_names, all_children, None)?;
394                out_uses.push(fdecl::Use::EventStream(fdecl::UseEventStream {
395                    source_name: Some(name),
396                    scope: match scopes {
397                        Some(values) => {
398                            let mut output = vec![];
399                            for value in &values {
400                                static EMPTY_SET: BTreeSet<&BorrowedName> = BTreeSet::new();
401                                if let Some(target) = translate_target_ref(
402                                    options,
403                                    value.into(),
404                                    &all_children,
405                                    &all_collections,
406                                    &EMPTY_SET,
407                                    Some(&TargetAvailability::Required),
408                                )? {
409                                    output.push(target);
410                                }
411                            }
412                            Some(output)
413                        }
414                        None => None,
415                    },
416                    source: Some(source),
417                    target_path: Some(
418                        annotate_type::<Vec<cm_types::Path>>(
419                            all_target_use_paths(use_, use_)
420                                .ok_or_else(|| Error::internal(internal_error.clone()))?
421                                .into(),
422                        )
423                        .iter()
424                        .next()
425                        .ok_or_else(|| Error::internal(internal_error.clone()))?
426                        .to_string(),
427                    ),
428                    filter: match use_.filter.clone() {
429                        Some(dict) => Some(dictionary_from_map(dict)?),
430                        None => None,
431                    },
432                    availability: Some(availability),
433                    ..Default::default()
434                }));
435            }
436        } else if let Some(n) = &use_.runner {
437            let (source, source_dictionary) =
438                extract_use_source(&options, use_, all_capability_names, all_children, None)?;
439            #[cfg(fuchsia_api_level_at_least = "HEAD")]
440            out_uses.push(fdecl::Use::Runner(fdecl::UseRunner {
441                source: Some(source),
442                source_name: Some(n.clone().into()),
443                source_dictionary,
444                ..Default::default()
445            }));
446        } else if let Some(n) = &use_.config {
447            let (source, source_dictionary) =
448                extract_use_source(&options, use_, all_capability_names, all_children, None)?;
449            let target = match &use_.key {
450                None => return Err(Error::validate("\"use config\" must have \"key\" field set.")),
451                Some(t) => t.clone(),
452            };
453            let availability = extract_use_availability(use_)?;
454            let type_ = validate::use_config_to_value_type(use_)?;
455
456            let default = if let Some(default) = &use_.config_default {
457                let value = config_value_file::field::config_value_from_json_value(
458                    default,
459                    &type_.clone().into(),
460                )
461                .map_err(|e| Error::InvalidArgs(format!("Error parsing config '{}': {}", n, e)))?;
462                Some(value.native_into_fidl())
463            } else {
464                None
465            };
466
467            out_uses.push(fdecl::Use::Config(fdecl::UseConfiguration {
468                source: Some(source),
469                source_name: Some(n.clone().into()),
470                target_name: Some(target.into()),
471                availability: Some(availability),
472                type_: Some(translate_value_type(&type_).0),
473                default,
474                source_dictionary,
475                ..Default::default()
476            }));
477        } else if let Some(n) = &use_.dictionary {
478            let (source, source_dictionary) =
479                extract_use_source(options, use_, all_capability_names, all_children, None)?;
480            let availability = extract_use_availability(use_)?;
481            for source_name in n.into_iter() {
482                out_uses.push(fdecl::Use::Dictionary(fdecl::UseDictionary {
483                    source: Some(source.clone()),
484                    source_name: Some(source_name.to_string()),
485                    source_dictionary: source_dictionary.clone(),
486                    target_path: Some(
487                        use_.path().as_ref().expect("no path on use dictionary").to_string(),
488                    ),
489                    dependency_type: Some(
490                        use_.dependency.clone().unwrap_or(cm::DependencyType::Strong).into(),
491                    ),
492                    availability: Some(availability),
493                    ..Default::default()
494                }));
495            }
496        } else {
497            return Err(Error::internal(format!("no capability in use declaration")));
498        };
499    }
500    Ok(out_uses)
501}
502
503/// `expose` rules route a single capability from one or more sources (self|framework|#<child>) to
504/// one or more targets (parent|framework).
505fn translate_expose(
506    options: &CompileOptions<'_>,
507    expose_in: &Vec<Expose>,
508    all_capability_names: &BTreeSet<&BorrowedName>,
509    all_collections: &BTreeSet<&BorrowedName>,
510    all_children: &BTreeSet<&BorrowedName>,
511) -> Result<Vec<fdecl::Expose>, Error> {
512    let mut out_exposes = vec![];
513    for expose in expose_in.iter() {
514        let target = extract_expose_target(expose);
515        if let Some(source_names) = expose.service() {
516            // When there are many `sources` exposed under the same `target_name`, aggregation
517            // will happen during routing.
518            let sources = extract_all_expose_sources(options, expose, Some(all_collections));
519            let target_names = all_target_capability_names(expose, expose)
520                .ok_or_else(|| Error::internal("no capability"))?;
521            for (source_name, target_name) in source_names.into_iter().zip(target_names.into_iter())
522            {
523                for (source, source_dictionary) in &sources {
524                    let DerivedSourceInfo { source, source_dictionary, availability } =
525                        derive_source_and_availability(
526                            expose.availability.as_ref(),
527                            source.clone(),
528                            source_dictionary.clone(),
529                            expose.source_availability.as_ref(),
530                            all_capability_names,
531                            all_children,
532                            all_collections,
533                        );
534                    out_exposes.push(fdecl::Expose::Service(fdecl::ExposeService {
535                        source: Some(source),
536                        source_name: Some(source_name.to_string()),
537                        source_dictionary,
538                        target_name: Some(target_name.to_string()),
539                        target: Some(target.clone()),
540                        availability: Some(availability),
541                        ..Default::default()
542                    }))
543                }
544            }
545        } else if let Some(n) = expose.protocol() {
546            let (source, source_dictionary) =
547                extract_single_expose_source(options, expose, Some(all_capability_names))?;
548            let source_names = n.into_iter();
549            let target_names = all_target_capability_names(expose, expose)
550                .ok_or_else(|| Error::internal("no capability"))?;
551            for (source_name, target_name) in source_names.into_iter().zip(target_names.into_iter())
552            {
553                let DerivedSourceInfo { source, source_dictionary, availability } =
554                    derive_source_and_availability(
555                        expose.availability.as_ref(),
556                        source.clone(),
557                        source_dictionary.clone(),
558                        expose.source_availability.as_ref(),
559                        all_capability_names,
560                        all_children,
561                        all_collections,
562                    );
563                out_exposes.push(fdecl::Expose::Protocol(fdecl::ExposeProtocol {
564                    source: Some(source),
565                    source_name: Some(source_name.to_string()),
566                    source_dictionary,
567                    target_name: Some(target_name.to_string()),
568                    target: Some(target.clone()),
569                    availability: Some(availability),
570                    ..Default::default()
571                }))
572            }
573        } else if let Some(n) = expose.directory() {
574            let (source, source_dictionary) = extract_single_expose_source(options, expose, None)?;
575            let source_names = n.into_iter();
576            let target_names = all_target_capability_names(expose, expose)
577                .ok_or_else(|| Error::internal("no capability"))?;
578            let rights = extract_expose_rights(expose)?;
579            let subdir = extract_expose_subdir(expose);
580            for (source_name, target_name) in source_names.into_iter().zip(target_names.into_iter())
581            {
582                let DerivedSourceInfo { source, source_dictionary, availability } =
583                    derive_source_and_availability(
584                        expose.availability.as_ref(),
585                        source.clone(),
586                        source_dictionary.clone(),
587                        expose.source_availability.as_ref(),
588                        all_capability_names,
589                        all_children,
590                        all_collections,
591                    );
592                out_exposes.push(fdecl::Expose::Directory(fdecl::ExposeDirectory {
593                    source: Some(source),
594                    source_name: Some(source_name.to_string()),
595                    source_dictionary,
596                    target_name: Some(target_name.to_string()),
597                    target: Some(target.clone()),
598                    rights,
599                    subdir: subdir.as_ref().map(|s| s.clone().into()),
600                    availability: Some(availability),
601                    ..Default::default()
602                }))
603            }
604        } else if let Some(n) = expose.runner() {
605            let (source, source_dictionary) = extract_single_expose_source(options, expose, None)?;
606            let source_names = n.into_iter();
607            let target_names = all_target_capability_names(expose, expose)
608                .ok_or_else(|| Error::internal("no capability"))?;
609            for (source_name, target_name) in source_names.into_iter().zip(target_names.into_iter())
610            {
611                out_exposes.push(fdecl::Expose::Runner(fdecl::ExposeRunner {
612                    source: Some(source.clone()),
613                    source_name: Some(source_name.to_string()),
614                    source_dictionary: source_dictionary.clone(),
615                    target: Some(target.clone()),
616                    target_name: Some(target_name.to_string()),
617                    ..Default::default()
618                }))
619            }
620        } else if let Some(n) = expose.resolver() {
621            let (source, source_dictionary) = extract_single_expose_source(options, expose, None)?;
622            let source_names = n.into_iter();
623            let target_names = all_target_capability_names(expose, expose)
624                .ok_or_else(|| Error::internal("no capability"))?;
625            for (source_name, target_name) in source_names.into_iter().zip(target_names.into_iter())
626            {
627                out_exposes.push(fdecl::Expose::Resolver(fdecl::ExposeResolver {
628                    source: Some(source.clone()),
629                    source_name: Some(source_name.to_string()),
630                    source_dictionary: source_dictionary.clone(),
631                    target: Some(target.clone()),
632                    target_name: Some(target_name.to_string()),
633                    ..Default::default()
634                }))
635            }
636        } else if let Some(n) = expose.dictionary() {
637            let (source, source_dictionary) = extract_single_expose_source(options, expose, None)?;
638            let source_names = n.into_iter();
639            let target_names = all_target_capability_names(expose, expose)
640                .ok_or_else(|| Error::internal("no capability"))?;
641            for (source_name, target_name) in source_names.into_iter().zip(target_names.into_iter())
642            {
643                let DerivedSourceInfo { source, source_dictionary, availability } =
644                    derive_source_and_availability(
645                        expose.availability.as_ref(),
646                        source.clone(),
647                        source_dictionary.clone(),
648                        expose.source_availability.as_ref(),
649                        all_capability_names,
650                        all_children,
651                        all_collections,
652                    );
653                out_exposes.push(fdecl::Expose::Dictionary(fdecl::ExposeDictionary {
654                    source: Some(source),
655                    source_name: Some(source_name.to_string()),
656                    source_dictionary,
657                    target_name: Some(target_name.to_string()),
658                    target: Some(target.clone()),
659                    availability: Some(availability),
660                    ..Default::default()
661                }))
662            }
663        } else if let Some(n) = expose.config() {
664            let (source, source_dictionary) = extract_single_expose_source(options, expose, None)?;
665            let source_names = n.into_iter();
666            let target_names = all_target_capability_names(expose, expose)
667                .ok_or_else(|| Error::internal("no capability"))?;
668            for (source_name, target_name) in source_names.into_iter().zip(target_names.into_iter())
669            {
670                let DerivedSourceInfo { source, source_dictionary, availability } =
671                    derive_source_and_availability(
672                        expose.availability.as_ref(),
673                        source.clone(),
674                        source_dictionary.clone(),
675                        expose.source_availability.as_ref(),
676                        all_capability_names,
677                        all_children,
678                        all_collections,
679                    );
680                out_exposes.push(fdecl::Expose::Config(fdecl::ExposeConfiguration {
681                    source: Some(source.clone()),
682                    source_name: Some(source_name.to_string()),
683                    source_dictionary,
684                    target: Some(target.clone()),
685                    target_name: Some(target_name.to_string()),
686                    availability: Some(availability),
687                    ..Default::default()
688                }))
689            }
690        } else {
691            return Err(Error::internal(format!("expose: must specify a known capability")));
692        }
693    }
694    Ok(out_exposes)
695}
696
697impl<T> Into<Vec<T>> for OneOrMany<T> {
698    fn into(self) -> Vec<T> {
699        match self {
700            OneOrMany::One(one) => vec![one],
701            OneOrMany::Many(many) => many,
702        }
703    }
704}
705
706/// Allows the above Into to work by annotating the type.
707fn annotate_type<T>(val: T) -> T {
708    val
709}
710
711struct DerivedSourceInfo {
712    source: fdecl::Ref,
713    source_dictionary: Option<String>,
714    availability: fdecl::Availability,
715}
716
717/// If the `source` is not found and `source_availability` is `Unknown`, returns a `Void` source.
718/// Otherwise, returns the source unchanged.
719fn derive_source_and_availability(
720    availability: Option<&Availability>,
721    source: fdecl::Ref,
722    source_dictionary: Option<String>,
723    source_availability: Option<&SourceAvailability>,
724    all_capability_names: &BTreeSet<&BorrowedName>,
725    all_children: &BTreeSet<&BorrowedName>,
726    all_collections: &BTreeSet<&BorrowedName>,
727) -> DerivedSourceInfo {
728    let availability = availability.map(|a| match a {
729        Availability::Required => fdecl::Availability::Required,
730        Availability::Optional => fdecl::Availability::Optional,
731        Availability::SameAsTarget => fdecl::Availability::SameAsTarget,
732        Availability::Transitional => fdecl::Availability::Transitional,
733    });
734    if source_availability != Some(&SourceAvailability::Unknown) {
735        return DerivedSourceInfo {
736            source,
737            source_dictionary,
738            availability: availability.unwrap_or(fdecl::Availability::Required),
739        };
740    }
741    match &source {
742        fdecl::Ref::Child(fdecl::ChildRef { name, .. })
743            if !all_children.contains(name.as_str()) =>
744        {
745            DerivedSourceInfo {
746                source: fdecl::Ref::VoidType(fdecl::VoidRef {}),
747                source_dictionary: None,
748                availability: availability.unwrap_or(fdecl::Availability::Optional),
749            }
750        }
751        fdecl::Ref::Collection(fdecl::CollectionRef { name, .. })
752            if !all_collections.contains(name.as_str()) =>
753        {
754            DerivedSourceInfo {
755                source: fdecl::Ref::VoidType(fdecl::VoidRef {}),
756                source_dictionary: None,
757                availability: availability.unwrap_or(fdecl::Availability::Optional),
758            }
759        }
760        fdecl::Ref::Capability(fdecl::CapabilityRef { name, .. })
761            if !all_capability_names.contains(name.as_str()) =>
762        {
763            DerivedSourceInfo {
764                source: fdecl::Ref::VoidType(fdecl::VoidRef {}),
765                source_dictionary: None,
766                availability: availability.unwrap_or(fdecl::Availability::Optional),
767            }
768        }
769        _ => DerivedSourceInfo {
770            source,
771            source_dictionary,
772            availability: availability.unwrap_or(fdecl::Availability::Required),
773        },
774    }
775}
776
777/// Emit a set of direct offers from `offer_to_all` for `target`, unless it
778/// overlaps with an offer in `direct_offers`.
779fn maybe_generate_direct_offer_from_all(
780    offer_to_all: &Offer,
781    direct_offers: &[Offer],
782    target: &BorrowedName,
783) -> Vec<Offer> {
784    assert!(offer_to_all.protocol.is_some() || offer_to_all.dictionary.is_some());
785    let mut returned_offers = vec![];
786    for mut local_offer in offer_to_all
787        .protocol
788        .as_ref()
789        .unwrap_or(&OneOrMany::Many(vec![]))
790        .iter()
791        .map(|individual_protocol| {
792            let mut local_offer = offer_to_all.clone();
793            local_offer.protocol = Some(OneOrMany::One(individual_protocol.clone()));
794            local_offer
795        })
796        .chain(
797            offer_to_all.dictionary.as_ref().unwrap_or(&OneOrMany::Many(vec![])).into_iter().map(
798                |dictionary| {
799                    let mut local_offer = offer_to_all.clone();
800                    local_offer.dictionary = Some(OneOrMany::One(dictionary.clone()));
801                    local_offer
802                },
803            ),
804        )
805    {
806        let disallowed_offer_source = OfferFromRef::Named(target.into());
807        if direct_offers.iter().all(|direct| {
808            // Assume that the cml being parsed is valid, which is the only
809            // way that this function errors
810            !offer_to_all_would_duplicate(&local_offer, direct, target).unwrap()
811        }) && !local_offer.from.iter().any(|from| from == &disallowed_offer_source)
812        {
813            local_offer.to = OneOrMany::One(OfferToRef::Named(target.into()));
814            returned_offers.push(local_offer);
815        }
816    }
817
818    returned_offers
819}
820
821fn expand_offer_to_all(
822    offers_in: &Vec<Offer>,
823    children: &BTreeSet<&BorrowedName>,
824    collections: &BTreeSet<&BorrowedName>,
825) -> Vec<Offer> {
826    let offers_to_all =
827        offers_in.iter().filter(|offer| matches!(offer.to, OneOrMany::One(OfferToRef::All)));
828
829    let mut direct_offers = offers_in
830        .iter()
831        .filter(|o| !matches!(o.to, OneOrMany::One(OfferToRef::All)))
832        .map(Offer::clone)
833        .collect::<Vec<Offer>>();
834
835    for offer_to_all in offers_to_all {
836        for target in children.iter().chain(collections.iter()) {
837            let offers = maybe_generate_direct_offer_from_all(offer_to_all, &direct_offers, target);
838            for offer in offers {
839                direct_offers.push(offer);
840            }
841        }
842    }
843
844    direct_offers
845}
846
847/// `offer` rules route multiple capabilities from multiple sources to multiple targets.
848fn translate_offer(
849    options: &CompileOptions<'_>,
850    offer_in: &Vec<Offer>,
851    all_capability_names: &BTreeSet<&BorrowedName>,
852    all_children: &BTreeSet<&BorrowedName>,
853    all_collections: &BTreeSet<&BorrowedName>,
854) -> Result<Vec<fdecl::Offer>, Error> {
855    let mut out_offers = vec![];
856    let expanded_offers = expand_offer_to_all(offer_in, all_children, all_collections);
857    for offer in &expanded_offers {
858        if let Some(n) = offer.service() {
859            let entries = extract_offer_sources_and_targets(
860                options,
861                offer,
862                n,
863                all_capability_names,
864                all_children,
865                all_collections,
866            )?;
867            for (source, source_dictionary, source_name, target, target_name) in entries {
868                let DerivedSourceInfo { source, source_dictionary, availability } =
869                    derive_source_and_availability(
870                        offer.availability.as_ref(),
871                        source,
872                        source_dictionary,
873                        offer.source_availability.as_ref(),
874                        all_capability_names,
875                        all_children,
876                        all_collections,
877                    );
878                out_offers.push(fdecl::Offer::Service(fdecl::OfferService {
879                    source: Some(source),
880                    source_name: Some(source_name.to_string()),
881                    source_dictionary,
882                    target: Some(target),
883                    target_name: Some(target_name.to_string()),
884                    availability: Some(availability),
885                    #[cfg(fuchsia_api_level_at_least = "HEAD")]
886                    dependency_type: Some(
887                        offer.dependency.clone().unwrap_or(cm::DependencyType::Strong).into(),
888                    ),
889                    ..Default::default()
890                }));
891            }
892        } else if let Some(n) = offer.protocol() {
893            let entries = extract_offer_sources_and_targets(
894                options,
895                offer,
896                n,
897                all_capability_names,
898                all_children,
899                all_collections,
900            )?;
901            for (source, source_dictionary, source_name, target, target_name) in entries {
902                let DerivedSourceInfo { source, source_dictionary, availability } =
903                    derive_source_and_availability(
904                        offer.availability.as_ref(),
905                        source,
906                        source_dictionary,
907                        offer.source_availability.as_ref(),
908                        all_capability_names,
909                        all_children,
910                        all_collections,
911                    );
912                out_offers.push(fdecl::Offer::Protocol(fdecl::OfferProtocol {
913                    source: Some(source),
914                    source_name: Some(source_name.to_string()),
915                    source_dictionary,
916                    target: Some(target),
917                    target_name: Some(target_name.to_string()),
918                    dependency_type: Some(
919                        offer.dependency.clone().unwrap_or(cm::DependencyType::Strong).into(),
920                    ),
921                    availability: Some(availability),
922                    ..Default::default()
923                }));
924            }
925        } else if let Some(n) = offer.directory() {
926            let entries = extract_offer_sources_and_targets(
927                options,
928                offer,
929                n,
930                all_capability_names,
931                all_children,
932                all_collections,
933            )?;
934            for (source, source_dictionary, source_name, target, target_name) in entries {
935                let DerivedSourceInfo { source, source_dictionary, availability } =
936                    derive_source_and_availability(
937                        offer.availability.as_ref(),
938                        source,
939                        source_dictionary,
940                        offer.source_availability.as_ref(),
941                        all_capability_names,
942                        all_children,
943                        all_collections,
944                    );
945                out_offers.push(fdecl::Offer::Directory(fdecl::OfferDirectory {
946                    source: Some(source),
947                    source_name: Some(source_name.to_string()),
948                    source_dictionary,
949                    target: Some(target),
950                    target_name: Some(target_name.to_string()),
951                    rights: extract_offer_rights(offer)?,
952                    subdir: extract_offer_subdir(offer).map(|s| s.into()),
953                    dependency_type: Some(
954                        offer.dependency.clone().unwrap_or(cm::DependencyType::Strong).into(),
955                    ),
956                    availability: Some(availability),
957                    ..Default::default()
958                }));
959            }
960        } else if let Some(n) = offer.storage() {
961            let entries = extract_offer_sources_and_targets(
962                options,
963                offer,
964                n,
965                all_capability_names,
966                all_children,
967                all_collections,
968            )?;
969            for (source, source_dictionary, source_name, target, target_name) in entries {
970                let DerivedSourceInfo { source, source_dictionary: _, availability } =
971                    derive_source_and_availability(
972                        offer.availability.as_ref(),
973                        source,
974                        source_dictionary,
975                        offer.source_availability.as_ref(),
976                        all_capability_names,
977                        all_children,
978                        all_collections,
979                    );
980                out_offers.push(fdecl::Offer::Storage(fdecl::OfferStorage {
981                    source: Some(source),
982                    source_name: Some(source_name.to_string()),
983                    target: Some(target),
984                    target_name: Some(target_name.to_string()),
985                    availability: Some(availability),
986                    ..Default::default()
987                }));
988            }
989        } else if let Some(n) = offer.runner() {
990            let entries = extract_offer_sources_and_targets(
991                options,
992                offer,
993                n,
994                all_capability_names,
995                all_children,
996                all_collections,
997            )?;
998            for (source, source_dictionary, source_name, target, target_name) in entries {
999                out_offers.push(fdecl::Offer::Runner(fdecl::OfferRunner {
1000                    source: Some(source),
1001                    source_name: Some(source_name.to_string()),
1002                    source_dictionary,
1003                    target: Some(target),
1004                    target_name: Some(target_name.to_string()),
1005                    ..Default::default()
1006                }));
1007            }
1008        } else if let Some(n) = offer.resolver() {
1009            let entries = extract_offer_sources_and_targets(
1010                options,
1011                offer,
1012                n,
1013                all_capability_names,
1014                all_children,
1015                all_collections,
1016            )?;
1017            for (source, source_dictionary, source_name, target, target_name) in entries {
1018                out_offers.push(fdecl::Offer::Resolver(fdecl::OfferResolver {
1019                    source: Some(source),
1020                    source_name: Some(source_name.to_string()),
1021                    source_dictionary,
1022                    target: Some(target),
1023                    target_name: Some(target_name.to_string()),
1024                    ..Default::default()
1025                }));
1026            }
1027        } else if let Some(n) = offer.event_stream() {
1028            let entries = extract_offer_sources_and_targets(
1029                options,
1030                offer,
1031                n,
1032                all_capability_names,
1033                all_children,
1034                all_collections,
1035            )?;
1036            for (source, source_dictionary, source_name, target, target_name) in entries {
1037                let DerivedSourceInfo { source, source_dictionary: _, availability } =
1038                    derive_source_and_availability(
1039                        offer.availability.as_ref(),
1040                        source,
1041                        source_dictionary,
1042                        offer.source_availability.as_ref(),
1043                        all_capability_names,
1044                        all_children,
1045                        all_collections,
1046                    );
1047                let scopes = match offer.scope.clone() {
1048                    Some(value) => Some(annotate_type::<Vec<EventScope>>(value.into())),
1049                    None => None,
1050                };
1051                out_offers.push(fdecl::Offer::EventStream(fdecl::OfferEventStream {
1052                    source: Some(source),
1053                    source_name: Some(source_name.to_string()),
1054                    target: Some(target),
1055                    target_name: Some(target_name.to_string()),
1056                    scope: match scopes {
1057                        Some(values) => {
1058                            let mut output = vec![];
1059                            for value in &values {
1060                                static EMPTY_SET: BTreeSet<&BorrowedName> = BTreeSet::new();
1061                                if let Some(target) = translate_target_ref(
1062                                    options,
1063                                    value.into(),
1064                                    &all_children,
1065                                    &all_collections,
1066                                    &EMPTY_SET,
1067                                    offer.target_availability.as_ref(),
1068                                )? {
1069                                    output.push(target);
1070                                }
1071                            }
1072                            Some(output)
1073                        }
1074                        None => None,
1075                    },
1076                    availability: Some(availability),
1077                    ..Default::default()
1078                }));
1079            }
1080        } else if let Some(n) = offer.dictionary() {
1081            let entries = extract_offer_sources_and_targets(
1082                options,
1083                offer,
1084                n,
1085                all_capability_names,
1086                all_children,
1087                all_collections,
1088            )?;
1089            for (source, source_dictionary, source_name, target, target_name) in entries {
1090                let DerivedSourceInfo { source, source_dictionary, availability } =
1091                    derive_source_and_availability(
1092                        offer.availability.as_ref(),
1093                        source,
1094                        source_dictionary,
1095                        offer.source_availability.as_ref(),
1096                        all_capability_names,
1097                        all_children,
1098                        all_collections,
1099                    );
1100                out_offers.push(fdecl::Offer::Dictionary(fdecl::OfferDictionary {
1101                    source: Some(source),
1102                    source_name: Some(source_name.to_string()),
1103                    source_dictionary,
1104                    target: Some(target),
1105                    target_name: Some(target_name.to_string()),
1106                    dependency_type: Some(
1107                        offer.dependency.clone().unwrap_or(cm::DependencyType::Strong).into(),
1108                    ),
1109                    availability: Some(availability),
1110                    ..Default::default()
1111                }));
1112            }
1113        } else if let Some(n) = offer.config() {
1114            let entries = extract_offer_sources_and_targets(
1115                options,
1116                offer,
1117                n,
1118                all_capability_names,
1119                all_children,
1120                all_collections,
1121            )?;
1122            for (source, source_dictionary, source_name, target, target_name) in entries {
1123                let DerivedSourceInfo { source, source_dictionary, availability } =
1124                    derive_source_and_availability(
1125                        offer.availability.as_ref(),
1126                        source,
1127                        source_dictionary,
1128                        offer.source_availability.as_ref(),
1129                        all_capability_names,
1130                        all_children,
1131                        all_collections,
1132                    );
1133                out_offers.push(fdecl::Offer::Config(fdecl::OfferConfiguration {
1134                    source: Some(source),
1135                    source_name: Some(source_name.to_string()),
1136                    target: Some(target),
1137                    target_name: Some(target_name.to_string()),
1138                    availability: Some(availability),
1139                    source_dictionary,
1140                    ..Default::default()
1141                }));
1142            }
1143        } else {
1144            return Err(Error::internal(format!("no capability")));
1145        }
1146    }
1147    Ok(out_offers)
1148}
1149
1150fn translate_children(children_in: &Vec<Child>) -> Vec<fdecl::Child> {
1151    let mut out_children = vec![];
1152    for child in children_in.iter() {
1153        out_children.push(fdecl::Child {
1154            name: Some(child.name.clone().into()),
1155            url: Some(child.url.clone().into()),
1156            startup: Some(child.startup.clone().into()),
1157            environment: extract_environment_ref(child.environment.as_ref()).map(|e| e.into()),
1158            on_terminate: child.on_terminate.as_ref().map(|r| r.clone().into()),
1159            ..Default::default()
1160        });
1161    }
1162    out_children
1163}
1164
1165fn translate_collections(collections_in: &Vec<Collection>) -> Vec<fdecl::Collection> {
1166    let mut out_collections = vec![];
1167    for collection in collections_in.iter() {
1168        out_collections.push(fdecl::Collection {
1169            name: Some(collection.name.clone().into()),
1170            durability: Some(collection.durability.clone().into()),
1171            environment: extract_environment_ref(collection.environment.as_ref()).map(|e| e.into()),
1172            allowed_offers: collection.allowed_offers.clone().map(|a| a.into()),
1173            allow_long_names: collection.allow_long_names.clone(),
1174            persistent_storage: collection.persistent_storage.clone(),
1175            ..Default::default()
1176        });
1177    }
1178    out_collections
1179}
1180
1181/// Translates a nested value type to a [`fuchsia.config.decl.ConfigType`]
1182fn translate_nested_value_type(nested_type: &ConfigNestedValueType) -> fdecl::ConfigType {
1183    let layout = match nested_type {
1184        ConfigNestedValueType::Bool {} => fdecl::ConfigTypeLayout::Bool,
1185        ConfigNestedValueType::Uint8 {} => fdecl::ConfigTypeLayout::Uint8,
1186        ConfigNestedValueType::Uint16 {} => fdecl::ConfigTypeLayout::Uint16,
1187        ConfigNestedValueType::Uint32 {} => fdecl::ConfigTypeLayout::Uint32,
1188        ConfigNestedValueType::Uint64 {} => fdecl::ConfigTypeLayout::Uint64,
1189        ConfigNestedValueType::Int8 {} => fdecl::ConfigTypeLayout::Int8,
1190        ConfigNestedValueType::Int16 {} => fdecl::ConfigTypeLayout::Int16,
1191        ConfigNestedValueType::Int32 {} => fdecl::ConfigTypeLayout::Int32,
1192        ConfigNestedValueType::Int64 {} => fdecl::ConfigTypeLayout::Int64,
1193        ConfigNestedValueType::String { .. } => fdecl::ConfigTypeLayout::String,
1194    };
1195    let constraints = match nested_type {
1196        ConfigNestedValueType::String { max_size } => {
1197            vec![fdecl::LayoutConstraint::MaxSize(max_size.get())]
1198        }
1199        _ => vec![],
1200    };
1201    fdecl::ConfigType {
1202        layout,
1203        constraints,
1204        // This optional is not necessary, but without it,
1205        // FIDL compilation complains because of a possible include-cycle.
1206        // Bug: https://fxbug.dev/42145148
1207        parameters: Some(vec![]),
1208    }
1209}
1210
1211/// Translates a value type to a [`fuchsia.sys2.ConfigType`]
1212fn translate_value_type(
1213    value_type: &ConfigValueType,
1214) -> (fdecl::ConfigType, fdecl::ConfigMutability) {
1215    let (layout, source_mutability) = match value_type {
1216        ConfigValueType::Bool { mutability } => (fdecl::ConfigTypeLayout::Bool, mutability),
1217        ConfigValueType::Uint8 { mutability } => (fdecl::ConfigTypeLayout::Uint8, mutability),
1218        ConfigValueType::Uint16 { mutability } => (fdecl::ConfigTypeLayout::Uint16, mutability),
1219        ConfigValueType::Uint32 { mutability } => (fdecl::ConfigTypeLayout::Uint32, mutability),
1220        ConfigValueType::Uint64 { mutability } => (fdecl::ConfigTypeLayout::Uint64, mutability),
1221        ConfigValueType::Int8 { mutability } => (fdecl::ConfigTypeLayout::Int8, mutability),
1222        ConfigValueType::Int16 { mutability } => (fdecl::ConfigTypeLayout::Int16, mutability),
1223        ConfigValueType::Int32 { mutability } => (fdecl::ConfigTypeLayout::Int32, mutability),
1224        ConfigValueType::Int64 { mutability } => (fdecl::ConfigTypeLayout::Int64, mutability),
1225        ConfigValueType::String { mutability, .. } => (fdecl::ConfigTypeLayout::String, mutability),
1226        ConfigValueType::Vector { mutability, .. } => (fdecl::ConfigTypeLayout::Vector, mutability),
1227    };
1228    let (constraints, parameters) = match value_type {
1229        ConfigValueType::String { max_size, .. } => {
1230            (vec![fdecl::LayoutConstraint::MaxSize(max_size.get())], vec![])
1231        }
1232        ConfigValueType::Vector { max_count, element, .. } => {
1233            let nested_type = translate_nested_value_type(element);
1234            (
1235                vec![fdecl::LayoutConstraint::MaxSize(max_count.get())],
1236                vec![fdecl::LayoutParameter::NestedType(nested_type)],
1237            )
1238        }
1239        _ => (vec![], vec![]),
1240    };
1241    let mut mutability = fdecl::ConfigMutability::empty();
1242    if let Some(source_mutability) = source_mutability {
1243        for source in source_mutability {
1244            match source {
1245                ConfigRuntimeSource::Parent => mutability |= fdecl::ConfigMutability::PARENT,
1246            }
1247        }
1248    }
1249    (
1250        fdecl::ConfigType {
1251            layout,
1252            constraints,
1253            // This optional is not necessary, but without it,
1254            // FIDL compilation complains because of a possible include-cycle.
1255            // Bug: https://fxbug.dev/42145148
1256            parameters: Some(parameters),
1257        },
1258        mutability,
1259    )
1260}
1261
1262/// Create the `fdecl::ConfigSchema` from the fields of the `config` block and the config capability
1263/// Use decls.
1264fn translate_config(
1265    fields: &Option<BTreeMap<ConfigKey, ConfigValueType>>,
1266    uses: &Option<Vec<Use>>,
1267    package_path: &Option<String>,
1268) -> Result<Option<fdecl::ConfigSchema>, Error> {
1269    let mut use_fields: BTreeMap<ConfigKey, ConfigValueType> = uses
1270        .iter()
1271        .flatten()
1272        .map(|u| {
1273            if u.config.is_none() {
1274                return None;
1275            }
1276            let key = ConfigKey(u.key.clone().expect("key should be set").into());
1277            let config_type =
1278                validate::use_config_to_value_type(u).expect("config type should be valid");
1279            Some((key, config_type))
1280        })
1281        .flatten()
1282        .collect();
1283    for (key, value) in fields.iter().flatten() {
1284        if use_fields.contains_key(key) {
1285            if use_fields.get(key) != Some(&value) {
1286                return Err(Error::validate(format!(
1287                    "Config error: `use` and `config` block contain key '{}' with different types",
1288                    key
1289                )));
1290            }
1291        }
1292        use_fields.insert(key.clone(), value.clone());
1293    }
1294
1295    if use_fields.is_empty() {
1296        return Ok(None);
1297    }
1298
1299    let source = match fields.as_ref().map_or(true, |f| f.is_empty()) {
1300        // If the config block is empty, we are using from capabilities.
1301        true => fdecl::ConfigValueSource::Capabilities(fdecl::ConfigSourceCapabilities::default()),
1302        // We aren't using config capabilities, check for the package path.
1303        _ => {
1304            let Some(package_path) = package_path.as_ref() else {
1305                return Err(Error::invalid_args(
1306                    "can't translate config: no package path for value file",
1307                ));
1308            };
1309            fdecl::ConfigValueSource::PackagePath(package_path.to_owned())
1310        }
1311    };
1312
1313    let mut fidl_fields = vec![];
1314
1315    // Compute a SHA-256 hash from each field
1316    let mut hasher = Sha256::new();
1317
1318    for (key, value) in &use_fields {
1319        let (type_, mutability) = translate_value_type(value);
1320
1321        fidl_fields.push(fdecl::ConfigField {
1322            key: Some(key.to_string()),
1323            type_: Some(type_),
1324            mutability: Some(mutability),
1325            ..Default::default()
1326        });
1327
1328        hasher.update(key.as_str());
1329
1330        value.update_digest(&mut hasher);
1331    }
1332
1333    let hash = hasher.finalize();
1334    let checksum = fdecl::ConfigChecksum::Sha256(*hash.as_ref());
1335
1336    Ok(Some(fdecl::ConfigSchema {
1337        fields: Some(fidl_fields),
1338        checksum: Some(checksum),
1339        value_source: Some(source),
1340        ..Default::default()
1341    }))
1342}
1343
1344fn translate_environments(
1345    options: &CompileOptions<'_>,
1346    envs_in: &Vec<Environment>,
1347    all_capability_names: &BTreeSet<&BorrowedName>,
1348) -> Result<Vec<fdecl::Environment>, Error> {
1349    envs_in
1350        .iter()
1351        .map(|env| {
1352            Ok(fdecl::Environment {
1353                name: Some(env.name.clone().into()),
1354                extends: match env.extends {
1355                    Some(EnvironmentExtends::Realm) => Some(fdecl::EnvironmentExtends::Realm),
1356                    Some(EnvironmentExtends::None) => Some(fdecl::EnvironmentExtends::None),
1357                    None => Some(fdecl::EnvironmentExtends::None),
1358                },
1359                runners: env
1360                    .runners
1361                    .as_ref()
1362                    .map(|runners| {
1363                        runners
1364                            .iter()
1365                            .map(|r| translate_runner_registration(options, r))
1366                            .collect::<Result<Vec<_>, Error>>()
1367                    })
1368                    .transpose()?,
1369                resolvers: env
1370                    .resolvers
1371                    .as_ref()
1372                    .map(|resolvers| {
1373                        resolvers
1374                            .iter()
1375                            .map(|r| translate_resolver_registration(options, r))
1376                            .collect::<Result<Vec<_>, Error>>()
1377                    })
1378                    .transpose()?,
1379                debug_capabilities: env
1380                    .debug
1381                    .as_ref()
1382                    .map(|debug_capabiltities| {
1383                        translate_debug_capabilities(
1384                            options,
1385                            debug_capabiltities,
1386                            all_capability_names,
1387                        )
1388                    })
1389                    .transpose()?,
1390                stop_timeout_ms: env.stop_timeout_ms.map(|s| s.0),
1391                ..Default::default()
1392            })
1393        })
1394        .collect()
1395}
1396
1397fn translate_runner_registration(
1398    options: &CompileOptions<'_>,
1399    reg: &RunnerRegistration,
1400) -> Result<fdecl::RunnerRegistration, Error> {
1401    let (source, _source_dictionary) = extract_single_offer_source(options, reg, None)?;
1402    Ok(fdecl::RunnerRegistration {
1403        source_name: Some(reg.runner.clone().into()),
1404        source: Some(source),
1405        target_name: Some(reg.r#as.as_ref().unwrap_or(&reg.runner).clone().into()),
1406        ..Default::default()
1407    })
1408}
1409
1410fn translate_resolver_registration(
1411    options: &CompileOptions<'_>,
1412    reg: &ResolverRegistration,
1413) -> Result<fdecl::ResolverRegistration, Error> {
1414    let (source, _source_dictionary) = extract_single_offer_source(options, reg, None)?;
1415    Ok(fdecl::ResolverRegistration {
1416        resolver: Some(reg.resolver.clone().into()),
1417        source: Some(source),
1418        scheme: Some(
1419            reg.scheme
1420                .as_str()
1421                .parse::<cm_types::UrlScheme>()
1422                .map_err(|e| Error::internal(format!("invalid URL scheme: {}", e)))?
1423                .into(),
1424        ),
1425        ..Default::default()
1426    })
1427}
1428
1429fn translate_debug_capabilities(
1430    options: &CompileOptions<'_>,
1431    capabilities: &Vec<DebugRegistration>,
1432    all_capability_names: &BTreeSet<&BorrowedName>,
1433) -> Result<Vec<fdecl::DebugRegistration>, Error> {
1434    let mut out_capabilities = vec![];
1435    for capability in capabilities {
1436        if let Some(n) = capability.protocol() {
1437            let (source, _source_dictionary) =
1438                extract_single_offer_source(options, capability, Some(all_capability_names))?;
1439            let targets = all_target_capability_names(capability, capability)
1440                .ok_or_else(|| Error::internal("no capability"))?;
1441            let source_names = n;
1442            for target_name in targets {
1443                // When multiple source names are provided, there is no way to alias each one, so
1444                // source_name == target_name.
1445                // When one source name is provided, source_name may be aliased to a different
1446                // target_name, so we source_names[0] to derive the source_name.
1447                //
1448                // TODO: This logic could be simplified to use iter::zip() if
1449                // extract_all_targets_for_each_child returned separate vectors for targets and
1450                // target_names instead of the cross product of them.
1451                let source_name = if source_names.len() == 1 {
1452                    *source_names.iter().next().unwrap()
1453                } else {
1454                    target_name
1455                };
1456                out_capabilities.push(fdecl::DebugRegistration::Protocol(
1457                    fdecl::DebugProtocolRegistration {
1458                        source: Some(source.clone()),
1459                        source_name: Some(source_name.to_string()),
1460                        target_name: Some(target_name.to_string()),
1461                        ..Default::default()
1462                    },
1463                ));
1464            }
1465        }
1466    }
1467    Ok(out_capabilities)
1468}
1469
1470fn extract_use_source(
1471    options: &CompileOptions<'_>,
1472    in_obj: &Use,
1473    all_capability_names: &BTreeSet<&BorrowedName>,
1474    all_children_names: &BTreeSet<&BorrowedName>,
1475    all_collection_names: Option<&BTreeSet<&BorrowedName>>,
1476) -> Result<(fdecl::Ref, Option<String>), Error> {
1477    let ref_ = match in_obj.from.as_ref() {
1478        Some(UseFromRef::Parent) => fdecl::Ref::Parent(fdecl::ParentRef {}),
1479        Some(UseFromRef::Framework) => fdecl::Ref::Framework(fdecl::FrameworkRef {}),
1480        Some(UseFromRef::Debug) => fdecl::Ref::Debug(fdecl::DebugRef {}),
1481        Some(UseFromRef::Self_) => fdecl::Ref::Self_(fdecl::SelfRef {}),
1482        Some(UseFromRef::Named(name)) => {
1483            if all_capability_names.contains(&name.as_ref()) {
1484                fdecl::Ref::Capability(fdecl::CapabilityRef { name: name.to_string() })
1485            } else if all_children_names.contains(&name.as_ref()) {
1486                fdecl::Ref::Child(fdecl::ChildRef { name: name.clone().into(), collection: None })
1487            } else if all_collection_names.is_some()
1488                && all_collection_names.unwrap().contains(&name.as_ref())
1489            {
1490                fdecl::Ref::Collection(fdecl::CollectionRef { name: name.to_string() })
1491            } else {
1492                return Err(Error::internal(format!(
1493                    "use source \"{:?}\" not supported for \"use from\"",
1494                    name
1495                )));
1496            }
1497        }
1498        Some(UseFromRef::Dictionary(d)) => {
1499            return Ok(dictionary_ref_to_source(&d));
1500        }
1501        None => fdecl::Ref::Parent(fdecl::ParentRef {}), // Default value.
1502    };
1503    Ok((ref_, None))
1504}
1505
1506fn extract_use_availability(in_obj: &Use) -> Result<fdecl::Availability, Error> {
1507    match in_obj.availability.as_ref() {
1508        Some(Availability::Required) | None => Ok(fdecl::Availability::Required),
1509        Some(Availability::Optional) => Ok(fdecl::Availability::Optional),
1510        Some(Availability::Transitional) => Ok(fdecl::Availability::Transitional),
1511        Some(Availability::SameAsTarget) => Err(Error::internal(
1512            "availability \"same_as_target\" not supported for use declarations",
1513        )),
1514    }
1515}
1516
1517fn extract_use_subdir(in_obj: &Use) -> Option<cm::RelativePath> {
1518    in_obj.subdir.clone()
1519}
1520
1521fn extract_expose_subdir(in_obj: &Expose) -> Option<cm::RelativePath> {
1522    in_obj.subdir.clone()
1523}
1524
1525fn extract_offer_subdir(in_obj: &Offer) -> Option<cm::RelativePath> {
1526    in_obj.subdir.clone()
1527}
1528
1529fn extract_expose_rights(in_obj: &Expose) -> Result<Option<fio::Operations>, Error> {
1530    match in_obj.rights.as_ref() {
1531        Some(rights_tokens) => {
1532            let mut rights = Vec::new();
1533            for token in rights_tokens.0.iter() {
1534                rights.append(&mut token.expand())
1535            }
1536            if rights.is_empty() {
1537                return Err(Error::missing_rights(
1538                    "Rights provided to expose are not well formed.",
1539                ));
1540            }
1541            let mut seen_rights = BTreeSet::new();
1542            let mut operations: fio::Operations = fio::Operations::empty();
1543            for right in rights.iter() {
1544                if seen_rights.contains(&right) {
1545                    return Err(Error::duplicate_rights(
1546                        "Rights provided to expose are not well formed.",
1547                    ));
1548                }
1549                seen_rights.insert(right);
1550                operations |= *right;
1551            }
1552
1553            Ok(Some(operations))
1554        }
1555        // Unlike use rights, expose rights can take a None value
1556        None => Ok(None),
1557    }
1558}
1559
1560fn expose_source_from_ref(
1561    options: &CompileOptions<'_>,
1562    reference: &ExposeFromRef,
1563    all_capability_names: Option<&BTreeSet<&BorrowedName>>,
1564    all_collections: Option<&BTreeSet<&BorrowedName>>,
1565) -> (fdecl::Ref, Option<String>) {
1566    let ref_ = match reference {
1567        ExposeFromRef::Named(name) => {
1568            if all_capability_names.is_some()
1569                && all_capability_names.unwrap().contains(&name.as_ref())
1570            {
1571                fdecl::Ref::Capability(fdecl::CapabilityRef { name: name.to_string() })
1572            } else if all_collections.is_some() && all_collections.unwrap().contains(&name.as_ref())
1573            {
1574                fdecl::Ref::Collection(fdecl::CollectionRef { name: name.to_string() })
1575            } else {
1576                fdecl::Ref::Child(fdecl::ChildRef { name: name.to_string(), collection: None })
1577            }
1578        }
1579        ExposeFromRef::Framework => fdecl::Ref::Framework(fdecl::FrameworkRef {}),
1580        ExposeFromRef::Self_ => fdecl::Ref::Self_(fdecl::SelfRef {}),
1581        ExposeFromRef::Void => fdecl::Ref::VoidType(fdecl::VoidRef {}),
1582        ExposeFromRef::Dictionary(d) => {
1583            return dictionary_ref_to_source(&d);
1584        }
1585    };
1586    (ref_, None)
1587}
1588
1589fn extract_single_expose_source(
1590    options: &CompileOptions<'_>,
1591    in_obj: &Expose,
1592    all_capability_names: Option<&BTreeSet<&BorrowedName>>,
1593) -> Result<(fdecl::Ref, Option<String>), Error> {
1594    match &in_obj.from {
1595        OneOrMany::One(reference) => {
1596            Ok(expose_source_from_ref(options, &reference, all_capability_names, None))
1597        }
1598        OneOrMany::Many(many) => Err(Error::internal(format!(
1599            "multiple unexpected \"from\" clauses for \"expose\": {:?}",
1600            many
1601        ))),
1602    }
1603}
1604
1605fn extract_all_expose_sources(
1606    options: &CompileOptions<'_>,
1607    in_obj: &Expose,
1608    all_collections: Option<&BTreeSet<&BorrowedName>>,
1609) -> Vec<(fdecl::Ref, Option<String>)> {
1610    in_obj.from.iter().map(|e| expose_source_from_ref(options, e, None, all_collections)).collect()
1611}
1612
1613fn extract_offer_rights(in_obj: &Offer) -> Result<Option<fio::Operations>, Error> {
1614    match in_obj.rights.as_ref() {
1615        Some(rights_tokens) => {
1616            let mut rights = Vec::new();
1617            for token in rights_tokens.0.iter() {
1618                rights.append(&mut token.expand())
1619            }
1620            if rights.is_empty() {
1621                return Err(Error::missing_rights("Rights provided to offer are not well formed."));
1622            }
1623            let mut seen_rights = BTreeSet::new();
1624            let mut operations: fio::Operations = fio::Operations::empty();
1625            for right in rights.iter() {
1626                if seen_rights.contains(&right) {
1627                    return Err(Error::duplicate_rights(
1628                        "Rights provided to offer are not well formed.",
1629                    ));
1630                }
1631                seen_rights.insert(right);
1632                operations |= *right;
1633            }
1634
1635            Ok(Some(operations))
1636        }
1637        // Unlike use rights, offer rights can take a None value
1638        None => Ok(None),
1639    }
1640}
1641
1642fn extract_single_offer_source<T>(
1643    options: &CompileOptions<'_>,
1644    in_obj: &T,
1645    all_capability_names: Option<&BTreeSet<&BorrowedName>>,
1646) -> Result<(fdecl::Ref, Option<String>), Error>
1647where
1648    T: FromClause,
1649{
1650    match in_obj.from_() {
1651        OneOrMany::One(reference) => {
1652            Ok(any_ref_to_decl(options, reference, all_capability_names, None))
1653        }
1654        many => {
1655            return Err(Error::internal(format!(
1656                "multiple unexpected \"from\" clauses for \"offer\": {}",
1657                many
1658            )));
1659        }
1660    }
1661}
1662
1663fn extract_all_offer_sources<T: FromClause>(
1664    options: &CompileOptions<'_>,
1665    in_obj: &T,
1666    all_capability_names: &BTreeSet<&BorrowedName>,
1667    all_collections: &BTreeSet<&BorrowedName>,
1668) -> Vec<(fdecl::Ref, Option<String>)> {
1669    in_obj
1670        .from_()
1671        .into_iter()
1672        .map(|r| {
1673            any_ref_to_decl(options, r.clone(), Some(all_capability_names), Some(all_collections))
1674        })
1675        .collect()
1676}
1677
1678fn translate_target_ref(
1679    options: &CompileOptions<'_>,
1680    reference: AnyRef<'_>,
1681    all_children: &BTreeSet<&BorrowedName>,
1682    all_collections: &BTreeSet<&BorrowedName>,
1683    all_capabilities: &BTreeSet<&BorrowedName>,
1684    target_availability: Option<&TargetAvailability>,
1685) -> Result<Option<fdecl::Ref>, Error> {
1686    match reference {
1687        AnyRef::Named(name) if all_children.contains(name) => {
1688            Ok(Some(fdecl::Ref::Child(fdecl::ChildRef {
1689                name: name.to_string(),
1690                collection: None,
1691            })))
1692        }
1693        AnyRef::Named(name) if all_collections.contains(name) => {
1694            Ok(Some(fdecl::Ref::Collection(fdecl::CollectionRef { name: name.to_string() })))
1695        }
1696        AnyRef::Named(name) if all_capabilities.contains(name) => {
1697            Ok(Some(fdecl::Ref::Capability(fdecl::CapabilityRef { name: name.to_string() })))
1698        }
1699        AnyRef::OwnDictionary(name) if all_capabilities.contains(name) => {
1700            Ok(Some(fdecl::Ref::Capability(fdecl::CapabilityRef { name: name.to_string() })))
1701        }
1702        AnyRef::Named(_) | AnyRef::OwnDictionary(_)
1703            if target_availability == Some(&TargetAvailability::Unknown) =>
1704        {
1705            Ok(None)
1706        }
1707        AnyRef::Named(_) => Err(Error::internal(format!("dangling reference: \"{}\"", reference))),
1708        _ => Err(Error::internal(format!("invalid child reference: \"{}\"", reference))),
1709    }
1710}
1711
1712// Return a list of (source, source capability id, source dictionary, target,
1713// target capability id) expressed in the `offer`.
1714fn extract_offer_sources_and_targets<'a>(
1715    options: &CompileOptions<'_>,
1716    offer: &'a Offer,
1717    source_names: OneOrMany<&'a BorrowedName>,
1718    all_capability_names: &BTreeSet<&'a BorrowedName>,
1719    all_children: &BTreeSet<&'a BorrowedName>,
1720    all_collections: &BTreeSet<&'a BorrowedName>,
1721) -> Result<Vec<(fdecl::Ref, Option<String>, &'a BorrowedName, fdecl::Ref, &'a BorrowedName)>, Error>
1722{
1723    let mut out = vec![];
1724
1725    let sources = extract_all_offer_sources(options, offer, all_capability_names, all_collections);
1726    let target_names = all_target_capability_names(offer, offer)
1727        .ok_or_else(|| Error::internal("no capability".to_string()))?;
1728
1729    for (source, source_dictionary) in sources {
1730        for to in &offer.to {
1731            for target_name in &target_names {
1732                // When multiple source names are provided, there is no way to alias each one,
1733                // so we can assume source_name == target_name.  When one source name is provided,
1734                // source_name may be aliased to a different target_name, so we use
1735                // source_names[0] to obtain the source_name.
1736                let source_name = if source_names.len() == 1 {
1737                    source_names.iter().next().unwrap()
1738                } else {
1739                    target_name
1740                };
1741                if let Some(target) = translate_target_ref(
1742                    options,
1743                    to.into(),
1744                    all_children,
1745                    all_collections,
1746                    all_capability_names,
1747                    offer.target_availability.as_ref(),
1748                )? {
1749                    out.push((
1750                        source.clone(),
1751                        source_dictionary.clone(),
1752                        *source_name,
1753                        target.clone(),
1754                        *target_name,
1755                    ));
1756                }
1757            }
1758        }
1759    }
1760    Ok(out)
1761}
1762
1763/// Return the target paths specified in the given use declaration.
1764fn all_target_use_paths<T, U>(in_obj: &T, to_obj: &U) -> Option<OneOrMany<Path>>
1765where
1766    T: CapabilityClause,
1767    U: PathClause,
1768{
1769    if let Some(n) = in_obj.service() {
1770        Some(svc_paths_from_names(n, to_obj))
1771    } else if let Some(n) = in_obj.protocol() {
1772        Some(svc_paths_from_names(n, to_obj))
1773    } else if let Some(_) = in_obj.directory() {
1774        let path = to_obj.path().expect("no path on use directory");
1775        Some(OneOrMany::One(path.clone()))
1776    } else if let Some(_) = in_obj.storage() {
1777        let path = to_obj.path().expect("no path on use storage");
1778        Some(OneOrMany::One(path.clone()))
1779    } else if let Some(_) = in_obj.event_stream() {
1780        let default_path = Path::new("/svc/fuchsia.component.EventStream").unwrap();
1781        let path = to_obj.path().unwrap_or(&default_path);
1782        Some(OneOrMany::One(path.clone()))
1783    } else {
1784        None
1785    }
1786}
1787
1788/// Returns the list of paths derived from a `use` declaration with `names` and `to_obj`. `to_obj`
1789/// must be a declaration that has a `path` clause.
1790fn svc_paths_from_names<T>(names: OneOrMany<&BorrowedName>, to_obj: &T) -> OneOrMany<Path>
1791where
1792    T: PathClause,
1793{
1794    match names {
1795        OneOrMany::One(n) => {
1796            if let Some(path) = to_obj.path() {
1797                OneOrMany::One(path.clone())
1798            } else {
1799                OneOrMany::One(format!("/svc/{}", n).parse().unwrap())
1800            }
1801        }
1802        OneOrMany::Many(v) => {
1803            let many = v.iter().map(|n| format!("/svc/{}", n).parse().unwrap()).collect();
1804            OneOrMany::Many(many)
1805        }
1806    }
1807}
1808
1809/// Return the single target path specified in the given use declaration.
1810fn one_target_use_path<T, U>(in_obj: &T, to_obj: &U) -> Result<Path, Error>
1811where
1812    T: CapabilityClause,
1813    U: PathClause,
1814{
1815    match all_target_use_paths(in_obj, to_obj) {
1816        Some(OneOrMany::One(target_name)) => Ok(target_name),
1817        Some(OneOrMany::Many(_)) => {
1818            Err(Error::internal("expecting one capability, but multiple provided"))
1819        }
1820        _ => Err(Error::internal("expecting one capability, but none provided")),
1821    }
1822}
1823
1824/// Return the target names or paths specified in the given capability.
1825fn all_target_capability_names<'a, T, U>(
1826    in_obj: &'a T,
1827    to_obj: &'a U,
1828) -> Option<OneOrMany<&'a BorrowedName>>
1829where
1830    T: CapabilityClause,
1831    U: AsClause + PathClause,
1832{
1833    if let Some(as_) = to_obj.r#as() {
1834        // We've already validated that when `as` is specified, only 1 source id exists.
1835        Some(OneOrMany::One(as_))
1836    } else {
1837        if let Some(n) = in_obj.service() {
1838            Some(n)
1839        } else if let Some(n) = in_obj.protocol() {
1840            Some(n)
1841        } else if let Some(n) = in_obj.directory() {
1842            Some(n)
1843        } else if let Some(n) = in_obj.storage() {
1844            Some(n)
1845        } else if let Some(n) = in_obj.runner() {
1846            Some(n)
1847        } else if let Some(n) = in_obj.resolver() {
1848            Some(n)
1849        } else if let Some(n) = in_obj.event_stream() {
1850            Some(n)
1851        } else if let Some(n) = in_obj.dictionary() {
1852            Some(n)
1853        } else if let Some(n) = in_obj.config() {
1854            Some(n)
1855        } else {
1856            None
1857        }
1858    }
1859}
1860
1861fn extract_expose_target(in_obj: &Expose) -> fdecl::Ref {
1862    match &in_obj.to {
1863        Some(ExposeToRef::Parent) => fdecl::Ref::Parent(fdecl::ParentRef {}),
1864        Some(ExposeToRef::Framework) => fdecl::Ref::Framework(fdecl::FrameworkRef {}),
1865        None => fdecl::Ref::Parent(fdecl::ParentRef {}),
1866    }
1867}
1868
1869fn extract_environment_ref(r: Option<&EnvironmentRef>) -> Option<cm::Name> {
1870    r.map(|r| {
1871        let EnvironmentRef::Named(name) = r;
1872        name.clone()
1873    })
1874}
1875
1876pub fn translate_capabilities(
1877    options: &CompileOptions<'_>,
1878    capabilities_in: &Vec<Capability>,
1879    as_builtin: bool,
1880) -> Result<Vec<fdecl::Capability>, Error> {
1881    let mut out_capabilities = vec![];
1882    for capability in capabilities_in {
1883        if let Some(service) = &capability.service {
1884            for n in service.iter() {
1885                let source_path = match as_builtin {
1886                    true => None,
1887                    false => Some(
1888                        capability
1889                            .path
1890                            .clone()
1891                            .unwrap_or_else(|| format!("/svc/{}", n).parse().unwrap())
1892                            .into(),
1893                    ),
1894                };
1895                out_capabilities.push(fdecl::Capability::Service(fdecl::Service {
1896                    name: Some(n.clone().into()),
1897                    source_path,
1898                    ..Default::default()
1899                }));
1900            }
1901        } else if let Some(protocol) = &capability.protocol {
1902            for n in protocol.iter() {
1903                let source_path = match as_builtin {
1904                    true => None,
1905                    false => Some(
1906                        capability
1907                            .path
1908                            .clone()
1909                            .unwrap_or_else(|| format!("/svc/{}", n).parse().unwrap())
1910                            .into(),
1911                    ),
1912                };
1913                out_capabilities.push(fdecl::Capability::Protocol(fdecl::Protocol {
1914                    name: Some(n.clone().into()),
1915                    source_path,
1916                    #[cfg(fuchsia_api_level_at_least = "HEAD")]
1917                    delivery: capability.delivery.map(Into::into),
1918                    ..Default::default()
1919                }));
1920            }
1921        } else if let Some(n) = &capability.directory {
1922            let source_path = match as_builtin {
1923                true => None,
1924                false => {
1925                    Some(capability.path.as_ref().expect("missing source path").clone().into())
1926                }
1927            };
1928            let rights = extract_required_rights(capability, "capability")?;
1929            out_capabilities.push(fdecl::Capability::Directory(fdecl::Directory {
1930                name: Some(n.clone().into()),
1931                source_path,
1932                rights: Some(rights),
1933                ..Default::default()
1934            }));
1935        } else if let Some(n) = &capability.storage {
1936            if as_builtin {
1937                return Err(Error::internal(format!(
1938                    "built-in storage capabilities are not supported"
1939                )));
1940            }
1941            let backing_dir = capability
1942                .backing_dir
1943                .as_ref()
1944                .expect("storage has no path or backing_dir")
1945                .clone()
1946                .into();
1947
1948            let (source, _source_dictionary) =
1949                any_ref_to_decl(options, capability.from.as_ref().unwrap().into(), None, None);
1950            out_capabilities.push(fdecl::Capability::Storage(fdecl::Storage {
1951                name: Some(n.clone().into()),
1952                backing_dir: Some(backing_dir),
1953                subdir: capability.subdir.clone().map(Into::into),
1954                source: Some(source),
1955                storage_id: Some(
1956                    capability.storage_id.clone().expect("storage is missing storage_id").into(),
1957                ),
1958                ..Default::default()
1959            }));
1960        } else if let Some(n) = &capability.runner {
1961            let source_path = match as_builtin {
1962                true => None,
1963                false => {
1964                    Some(capability.path.as_ref().expect("missing source path").clone().into())
1965                }
1966            };
1967            out_capabilities.push(fdecl::Capability::Runner(fdecl::Runner {
1968                name: Some(n.clone().into()),
1969                source_path,
1970                ..Default::default()
1971            }));
1972        } else if let Some(n) = &capability.resolver {
1973            let source_path = match as_builtin {
1974                true => None,
1975                false => {
1976                    Some(capability.path.as_ref().expect("missing source path").clone().into())
1977                }
1978            };
1979            out_capabilities.push(fdecl::Capability::Resolver(fdecl::Resolver {
1980                name: Some(n.clone().into()),
1981                source_path,
1982                ..Default::default()
1983            }));
1984        } else if let Some(ns) = &capability.event_stream {
1985            if !as_builtin {
1986                return Err(Error::internal(format!(
1987                    "event_stream capabilities may only be declared as built-in capabilities"
1988                )));
1989            }
1990            for n in ns {
1991                out_capabilities.push(fdecl::Capability::EventStream(fdecl::EventStream {
1992                    name: Some(n.clone().into()),
1993                    ..Default::default()
1994                }));
1995            }
1996        } else if let Some(n) = &capability.dictionary {
1997            out_capabilities.push(fdecl::Capability::Dictionary(fdecl::Dictionary {
1998                name: Some(n.clone().into()),
1999                source_path: capability.path.clone().map(Into::into),
2000                ..Default::default()
2001            }));
2002        } else if let Some(c) = &capability.config {
2003            let value =
2004                configuration_to_value(c, &capability, &capability.config_type, &capability.value)?;
2005            out_capabilities.push(fdecl::Capability::Config(fdecl::Configuration {
2006                name: Some(c.clone().into()),
2007                value: Some(value),
2008                ..Default::default()
2009            }));
2010        } else {
2011            return Err(Error::internal(format!("no capability declaration recognized")));
2012        }
2013    }
2014    Ok(out_capabilities)
2015}
2016
2017pub fn extract_required_rights<T>(in_obj: &T, keyword: &str) -> Result<fio::Operations, Error>
2018where
2019    T: RightsClause,
2020{
2021    match in_obj.rights() {
2022        Some(rights_tokens) => {
2023            let mut rights = Vec::new();
2024            for token in rights_tokens.0.iter() {
2025                rights.append(&mut token.expand())
2026            }
2027            if rights.is_empty() {
2028                return Err(Error::missing_rights(format!(
2029                    "Rights provided to `{}` are not well formed.",
2030                    keyword
2031                )));
2032            }
2033            let mut seen_rights = BTreeSet::new();
2034            let mut operations: fio::Operations = fio::Operations::empty();
2035            for right in rights.iter() {
2036                if seen_rights.contains(&right) {
2037                    return Err(Error::duplicate_rights(format!(
2038                        "Rights provided to `{}` are not well formed.",
2039                        keyword
2040                    )));
2041                }
2042                seen_rights.insert(right);
2043                operations |= *right;
2044            }
2045
2046            Ok(operations)
2047        }
2048        None => Err(Error::internal(format!(
2049            "No `{}` rights provided but required for directories",
2050            keyword
2051        ))),
2052    }
2053}
2054
2055/// Takes an `AnyRef` and returns the `fdecl::Ref` equivalent and the dictionary path, if
2056/// the ref was a dictionary ref.
2057pub fn any_ref_to_decl(
2058    options: &CompileOptions<'_>,
2059    reference: AnyRef<'_>,
2060    all_capability_names: Option<&BTreeSet<&BorrowedName>>,
2061    all_collection_names: Option<&BTreeSet<&BorrowedName>>,
2062) -> (fdecl::Ref, Option<String>) {
2063    let ref_ = match reference {
2064        AnyRef::Named(name) => {
2065            if all_capability_names.is_some() && all_capability_names.unwrap().contains(name) {
2066                fdecl::Ref::Capability(fdecl::CapabilityRef { name: name.to_string() })
2067            } else if all_collection_names.is_some() && all_collection_names.unwrap().contains(name)
2068            {
2069                fdecl::Ref::Collection(fdecl::CollectionRef { name: name.to_string() })
2070            } else {
2071                fdecl::Ref::Child(fdecl::ChildRef { name: name.to_string(), collection: None })
2072            }
2073        }
2074        AnyRef::Framework => fdecl::Ref::Framework(fdecl::FrameworkRef {}),
2075        AnyRef::Debug => fdecl::Ref::Debug(fdecl::DebugRef {}),
2076        AnyRef::Parent => fdecl::Ref::Parent(fdecl::ParentRef {}),
2077        AnyRef::Self_ => fdecl::Ref::Self_(fdecl::SelfRef {}),
2078        AnyRef::Void => fdecl::Ref::VoidType(fdecl::VoidRef {}),
2079        AnyRef::Dictionary(d) => {
2080            return dictionary_ref_to_source(&d);
2081        }
2082        AnyRef::OwnDictionary(name) => {
2083            fdecl::Ref::Capability(fdecl::CapabilityRef { name: name.to_string() })
2084        }
2085    };
2086    (ref_, None)
2087}
2088
2089/// Takes a `DictionaryRef` and returns the `fdecl::Ref` equivalent and the dictionary path.
2090fn dictionary_ref_to_source(d: &DictionaryRef) -> (fdecl::Ref, Option<String>) {
2091    #[allow(unused)]
2092    let root = match &d.root {
2093        RootDictionaryRef::Named(name) => {
2094            fdecl::Ref::Child(fdecl::ChildRef { name: name.clone().into(), collection: None })
2095        }
2096        RootDictionaryRef::Parent => fdecl::Ref::Parent(fdecl::ParentRef {}),
2097        RootDictionaryRef::Self_ => fdecl::Ref::Self_(fdecl::SelfRef {}),
2098    };
2099    (root, Some(d.path.to_string()))
2100}
2101
2102fn configuration_to_value(
2103    name: &BorrowedName,
2104    capability: &Capability,
2105    config_type: &Option<ConfigType>,
2106    value: &Option<serde_json::Value>,
2107) -> Result<fdecl::ConfigValue, Error> {
2108    let Some(config_type) = config_type.as_ref() else {
2109        return Err(Error::InvalidArgs(format!(
2110            "Configuration field '{}' must have 'type' set",
2111            name
2112        )));
2113    };
2114    let Some(value) = value.as_ref() else {
2115        return Err(Error::InvalidArgs(format!(
2116            "Configuration field '{}' must have 'value' set",
2117            name
2118        )));
2119    };
2120
2121    let config_type = match config_type {
2122        ConfigType::Bool => cm_rust::ConfigValueType::Bool,
2123        ConfigType::Uint8 => cm_rust::ConfigValueType::Uint8,
2124        ConfigType::Uint16 => cm_rust::ConfigValueType::Uint16,
2125        ConfigType::Uint32 => cm_rust::ConfigValueType::Uint32,
2126        ConfigType::Uint64 => cm_rust::ConfigValueType::Uint64,
2127        ConfigType::Int8 => cm_rust::ConfigValueType::Int8,
2128        ConfigType::Int16 => cm_rust::ConfigValueType::Int16,
2129        ConfigType::Int32 => cm_rust::ConfigValueType::Int32,
2130        ConfigType::Int64 => cm_rust::ConfigValueType::Int64,
2131        ConfigType::String => {
2132            let Some(max_size) = capability.config_max_size else {
2133                return Err(Error::InvalidArgs(format!(
2134                    "Configuration field '{}' must have 'max_size' set",
2135                    name
2136                )));
2137            };
2138            cm_rust::ConfigValueType::String { max_size: max_size.into() }
2139        }
2140        ConfigType::Vector => {
2141            let Some(ref element) = capability.config_element_type else {
2142                return Err(Error::InvalidArgs(format!(
2143                    "Configuration field '{}' must have 'element_type' set",
2144                    name
2145                )));
2146            };
2147            let Some(max_count) = capability.config_max_count else {
2148                return Err(Error::InvalidArgs(format!(
2149                    "Configuration field '{}' must have 'max_count' set",
2150                    name
2151                )));
2152            };
2153            let nested_type = match element {
2154                ConfigNestedValueType::Bool { .. } => cm_rust::ConfigNestedValueType::Bool,
2155                ConfigNestedValueType::Uint8 { .. } => cm_rust::ConfigNestedValueType::Uint8,
2156                ConfigNestedValueType::Uint16 { .. } => cm_rust::ConfigNestedValueType::Uint16,
2157                ConfigNestedValueType::Uint32 { .. } => cm_rust::ConfigNestedValueType::Uint32,
2158                ConfigNestedValueType::Uint64 { .. } => cm_rust::ConfigNestedValueType::Uint64,
2159                ConfigNestedValueType::Int8 { .. } => cm_rust::ConfigNestedValueType::Int8,
2160                ConfigNestedValueType::Int16 { .. } => cm_rust::ConfigNestedValueType::Int16,
2161                ConfigNestedValueType::Int32 { .. } => cm_rust::ConfigNestedValueType::Int32,
2162                ConfigNestedValueType::Int64 { .. } => cm_rust::ConfigNestedValueType::Int64,
2163                ConfigNestedValueType::String { max_size } => {
2164                    cm_rust::ConfigNestedValueType::String { max_size: (*max_size).into() }
2165                }
2166            };
2167            cm_rust::ConfigValueType::Vector { max_count: max_count.into(), nested_type }
2168        }
2169    };
2170    let value = config_value_file::field::config_value_from_json_value(value, &config_type)
2171        .map_err(|e| Error::InvalidArgs(format!("Error parsing config '{}': {}", name, e)))?;
2172    Ok(value.native_into_fidl())
2173}
2174
2175#[cfg(test)]
2176pub mod test_util {
2177    /// Construct a CML [Document] from the provided JSON literal expression or panic if error.
2178    macro_rules! must_parse_cml {
2179        ($($input:tt)+) => {
2180            serde_json::from_str::<Document>(&json!($($input)+).to_string())
2181                .expect("deserialization failed")
2182        };
2183    }
2184    pub(crate) use must_parse_cml;
2185}
2186
2187#[cfg(test)]
2188mod tests {
2189    use super::*;
2190    use crate::error::Error;
2191    use crate::features::Feature;
2192    use crate::translate::test_util::must_parse_cml;
2193    use crate::{
2194        AsClause, CapabilityClause, Document, FromClause, OneOrMany, Path, PathClause, Program,
2195        create_offer,
2196    };
2197    use assert_matches::assert_matches;
2198    use cm_fidl_validator::error::{AvailabilityList, DeclField, Error as CmFidlError, ErrorList};
2199    use cm_types::{self as cm, Name};
2200    use difference::Changeset;
2201    use serde_json::{Map, Value, json};
2202    use std::collections::BTreeSet;
2203    use std::convert::Into;
2204    use std::str::FromStr;
2205    use {
2206        fidl_fuchsia_component_decl as fdecl, fidl_fuchsia_data as fdata, fidl_fuchsia_io as fio,
2207    };
2208
2209    macro_rules! test_compile {
2210        (
2211            $(
2212                $(#[$m:meta])*
2213                $test_name:ident => {
2214                    $(features = $features:expr,)?
2215                    input = $input:expr,
2216                    output = $expected:expr,
2217                },
2218            )+
2219        ) => {
2220            $(
2221                $(#[$m])*
2222                #[test]
2223                fn $test_name() {
2224                    let input = serde_json::from_str(&$input.to_string()).expect("deserialization failed");
2225                    let options = CompileOptions::new().config_package_path("fake.cvf");
2226                    // Optionally specify features.
2227                    $(let features = $features; let options = options.features(&features);)?
2228                    let actual = compile(&input, options).expect("compilation failed");
2229                    if actual != $expected {
2230                        let e = format!("{:#?}", $expected);
2231                        let a = format!("{:#?}", actual);
2232                        panic!("{}", Changeset::new(&a, &e, "\n"));
2233                    }
2234                }
2235            )+
2236        };
2237    }
2238
2239    fn default_component_decl() -> fdecl::Component {
2240        fdecl::Component::default()
2241    }
2242
2243    test_compile! {
2244        test_compile_empty => {
2245            input = json!({}),
2246            output = default_component_decl(),
2247        },
2248
2249        test_compile_empty_includes => {
2250            input = json!({ "include": [] }),
2251            output = default_component_decl(),
2252        },
2253
2254        test_compile_offer_to_all_and_diff_sources => {
2255            input = json!({
2256                "children": [
2257                    {
2258                        "name": "logger",
2259                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
2260                    },
2261                ],
2262                "collections": [
2263                    {
2264                        "name": "coll",
2265                        "durability": "transient",
2266                    },
2267                ],
2268                "offer": [
2269                    {
2270                        "protocol": "fuchsia.logger.LogSink",
2271                        "from": "parent",
2272                        "to": "all",
2273                    },
2274                    {
2275                        "protocol": "fuchsia.logger.LogSink",
2276                        "from": "framework",
2277                        "to": "#logger",
2278                        "as": "LogSink2",
2279                    },
2280                ],
2281            }),
2282            output = fdecl::Component {
2283                offers: Some(vec![
2284                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2285                        source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef {})),
2286                        source_name: Some("fuchsia.logger.LogSink".into()),
2287                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2288                            name: "logger".into(),
2289                            collection: None,
2290                        })),
2291                        target_name: Some("LogSink2".into()),
2292                        dependency_type: Some(fdecl::DependencyType::Strong),
2293                        availability: Some(fdecl::Availability::Required),
2294                        ..Default::default()
2295                    }),
2296                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2297                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2298                        source_name: Some("fuchsia.logger.LogSink".into()),
2299                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2300                            name: "logger".into(),
2301                            collection: None,
2302                        })),
2303                        target_name: Some("fuchsia.logger.LogSink".into()),
2304                        dependency_type: Some(fdecl::DependencyType::Strong),
2305                        availability: Some(fdecl::Availability::Required),
2306                        ..Default::default()
2307                    }),
2308                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2309                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2310                        source_name: Some("fuchsia.logger.LogSink".into()),
2311                        target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
2312                            name: "coll".into(),
2313                        })),
2314                        target_name: Some("fuchsia.logger.LogSink".into()),
2315                        dependency_type: Some(fdecl::DependencyType::Strong),
2316                        availability: Some(fdecl::Availability::Required),
2317                        ..Default::default()
2318                    }),
2319                ]),
2320                children: Some(vec![fdecl::Child {
2321                    name: Some("logger".into()),
2322                    url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".into()),
2323                    startup: Some(fdecl::StartupMode::Lazy),
2324                    ..Default::default()
2325                }]),
2326                collections: Some(vec![fdecl::Collection {
2327                    name: Some("coll".into()),
2328                    durability: Some(fdecl::Durability::Transient),
2329                    ..Default::default()
2330                }]),
2331                ..default_component_decl()
2332            },
2333        },
2334
2335        test_compile_offer_to_all => {
2336            input = json!({
2337                "children": [
2338                    {
2339                        "name": "logger",
2340                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
2341                    },
2342                    {
2343                        "name": "something",
2344                        "url": "fuchsia-pkg://fuchsia.com/something/stable#meta/something.cm",
2345                    },
2346                ],
2347                "collections": [
2348                    {
2349                        "name": "coll",
2350                        "durability": "transient",
2351                    },
2352                ],
2353                "offer": [
2354                    {
2355                        "protocol": "fuchsia.logger.LogSink",
2356                        "from": "parent",
2357                        "to": "all",
2358                    },
2359                    {
2360                        "protocol": "fuchsia.inspect.InspectSink",
2361                        "from": "parent",
2362                        "to": "all",
2363                    },
2364                    {
2365                        "protocol": "fuchsia.logger.LegacyLog",
2366                        "from": "parent",
2367                        "to": "#logger",
2368                    },
2369                ],
2370            }),
2371            output = fdecl::Component {
2372                offers: Some(vec![
2373                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2374                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2375                        source_name: Some("fuchsia.logger.LegacyLog".into()),
2376                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2377                            name: "logger".into(),
2378                            collection: None,
2379                        })),
2380                        target_name: Some("fuchsia.logger.LegacyLog".into()),
2381                        dependency_type: Some(fdecl::DependencyType::Strong),
2382                        availability: Some(fdecl::Availability::Required),
2383                        ..Default::default()
2384                    }),
2385                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2386                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2387                        source_name: Some("fuchsia.logger.LogSink".into()),
2388                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2389                            name: "logger".into(),
2390                            collection: None,
2391                        })),
2392                        target_name: Some("fuchsia.logger.LogSink".into()),
2393                        dependency_type: Some(fdecl::DependencyType::Strong),
2394                        availability: Some(fdecl::Availability::Required),
2395                        ..Default::default()
2396                    }),
2397                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2398                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2399                        source_name: Some("fuchsia.logger.LogSink".into()),
2400                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2401                            name: "something".into(),
2402                            collection: None,
2403                        })),
2404                        target_name: Some("fuchsia.logger.LogSink".into()),
2405                        dependency_type: Some(fdecl::DependencyType::Strong),
2406                        availability: Some(fdecl::Availability::Required),
2407                        ..Default::default()
2408                    }),
2409                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2410                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2411                        source_name: Some("fuchsia.logger.LogSink".into()),
2412                        target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
2413                            name: "coll".into(),
2414                        })),
2415                        target_name: Some("fuchsia.logger.LogSink".into()),
2416                        dependency_type: Some(fdecl::DependencyType::Strong),
2417                        availability: Some(fdecl::Availability::Required),
2418                        ..Default::default()
2419                    }),
2420                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2421                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2422                        source_name: Some("fuchsia.inspect.InspectSink".into()),
2423                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2424                            name: "logger".into(),
2425                            collection: None,
2426                        })),
2427                        target_name: Some("fuchsia.inspect.InspectSink".into()),
2428                        dependency_type: Some(fdecl::DependencyType::Strong),
2429                        availability: Some(fdecl::Availability::Required),
2430                        ..Default::default()
2431                    }),
2432                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2433                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2434                        source_name: Some("fuchsia.inspect.InspectSink".into()),
2435                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2436                            name: "something".into(),
2437                            collection: None,
2438                        })),
2439                        target_name: Some("fuchsia.inspect.InspectSink".into()),
2440                        dependency_type: Some(fdecl::DependencyType::Strong),
2441                        availability: Some(fdecl::Availability::Required),
2442                        ..Default::default()
2443                    }),
2444                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2445                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2446                        source_name: Some("fuchsia.inspect.InspectSink".into()),
2447                        target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
2448                            name: "coll".into(),
2449                        })),
2450                        target_name: Some("fuchsia.inspect.InspectSink".into()),
2451                        dependency_type: Some(fdecl::DependencyType::Strong),
2452                        availability: Some(fdecl::Availability::Required),
2453                        ..Default::default()
2454                    }),
2455                ]),
2456                children: Some(vec![
2457                    fdecl::Child {
2458                        name: Some("logger".into()),
2459                        url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".into()),
2460                        startup: Some(fdecl::StartupMode::Lazy),
2461                        ..Default::default()
2462                    },
2463                    fdecl::Child {
2464                        name: Some("something".into()),
2465                        url: Some(
2466                            "fuchsia-pkg://fuchsia.com/something/stable#meta/something.cm".into(),
2467                        ),
2468                        startup: Some(fdecl::StartupMode::Lazy),
2469                        ..Default::default()
2470                    },
2471                ]),
2472                collections: Some(vec![fdecl::Collection {
2473                    name: Some("coll".into()),
2474                    durability: Some(fdecl::Durability::Transient),
2475                    ..Default::default()
2476                }]),
2477                ..default_component_decl()
2478            },
2479        },
2480
2481        test_compile_offer_to_all_hides_individual_duplicate_routes => {
2482            input = json!({
2483                "children": [
2484                    {
2485                        "name": "logger",
2486                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
2487                    },
2488                    {
2489                        "name": "something",
2490                        "url": "fuchsia-pkg://fuchsia.com/something/stable#meta/something.cm",
2491                    },
2492                    {
2493                        "name": "something-v2",
2494                        "url": "fuchsia-pkg://fuchsia.com/something/stable#meta/something-v2.cm",
2495                    },
2496                ],
2497                "collections": [
2498                    {
2499                        "name": "coll",
2500                        "durability": "transient",
2501                    },
2502                    {
2503                        "name": "coll2",
2504                        "durability": "transient",
2505                    },
2506                ],
2507                "offer": [
2508                    {
2509                        "protocol": "fuchsia.logger.LogSink",
2510                        "from": "parent",
2511                        "to": "#logger",
2512                    },
2513                    {
2514                        "protocol": "fuchsia.logger.LogSink",
2515                        "from": "parent",
2516                        "to": "all",
2517                    },
2518                    {
2519                        "protocol": "fuchsia.logger.LogSink",
2520                        "from": "parent",
2521                        "to": [ "#something", "#something-v2", "#coll2"],
2522                    },
2523                    {
2524                        "protocol": "fuchsia.logger.LogSink",
2525                        "from": "parent",
2526                        "to": "#coll",
2527                    },
2528                ],
2529            }),
2530            output = fdecl::Component {
2531                offers: Some(vec![
2532                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2533                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2534                        source_name: Some("fuchsia.logger.LogSink".into()),
2535                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2536                            name: "logger".into(),
2537                            collection: None,
2538                        })),
2539                        target_name: Some("fuchsia.logger.LogSink".into()),
2540                        dependency_type: Some(fdecl::DependencyType::Strong),
2541                        availability: Some(fdecl::Availability::Required),
2542                        ..Default::default()
2543                    }),
2544                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2545                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2546                        source_name: Some("fuchsia.logger.LogSink".into()),
2547                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2548                            name: "something".into(),
2549                            collection: None,
2550                        })),
2551                        target_name: Some("fuchsia.logger.LogSink".into()),
2552                        dependency_type: Some(fdecl::DependencyType::Strong),
2553                        availability: Some(fdecl::Availability::Required),
2554                        ..Default::default()
2555                    }),
2556                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2557                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2558                        source_name: Some("fuchsia.logger.LogSink".into()),
2559                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2560                            name: "something-v2".into(),
2561                            collection: None,
2562                        })),
2563                        target_name: Some("fuchsia.logger.LogSink".into()),
2564                        dependency_type: Some(fdecl::DependencyType::Strong),
2565                        availability: Some(fdecl::Availability::Required),
2566                        ..Default::default()
2567                    }),
2568                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2569                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2570                        source_name: Some("fuchsia.logger.LogSink".into()),
2571                        target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
2572                            name: "coll2".into(),
2573                        })),
2574                        target_name: Some("fuchsia.logger.LogSink".into()),
2575                        dependency_type: Some(fdecl::DependencyType::Strong),
2576                        availability: Some(fdecl::Availability::Required),
2577                        ..Default::default()
2578                    }),
2579                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2580                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2581                        source_name: Some("fuchsia.logger.LogSink".into()),
2582                        target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
2583                            name: "coll".into(),
2584                        })),
2585                        target_name: Some("fuchsia.logger.LogSink".into()),
2586                        dependency_type: Some(fdecl::DependencyType::Strong),
2587                        availability: Some(fdecl::Availability::Required),
2588                        ..Default::default()
2589                    }),
2590                ]),
2591                children: Some(vec![
2592                    fdecl::Child {
2593                        name: Some("logger".into()),
2594                        url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".into()),
2595                        startup: Some(fdecl::StartupMode::Lazy),
2596                        ..Default::default()
2597                    },
2598                    fdecl::Child {
2599                        name: Some("something".into()),
2600                        url: Some(
2601                            "fuchsia-pkg://fuchsia.com/something/stable#meta/something.cm".into(),
2602                        ),
2603                        startup: Some(fdecl::StartupMode::Lazy),
2604                        ..Default::default()
2605                    },
2606                    fdecl::Child {
2607                        name: Some("something-v2".into()),
2608                        url: Some(
2609                            "fuchsia-pkg://fuchsia.com/something/stable#meta/something-v2.cm".into(),
2610                        ),
2611                        startup: Some(fdecl::StartupMode::Lazy),
2612                        ..Default::default()
2613                    },
2614                ]),
2615                collections: Some(vec![fdecl::Collection {
2616                    name: Some("coll".into()),
2617                    durability: Some(fdecl::Durability::Transient),
2618                    ..Default::default()
2619                }, fdecl::Collection {
2620                    name: Some("coll2".into()),
2621                    durability: Some(fdecl::Durability::Transient),
2622                    ..Default::default()
2623                }]),
2624                ..default_component_decl()
2625            },
2626        },
2627
2628        test_compile_offer_to_all_from_child => {
2629            input = json!({
2630                "children": [
2631                    {
2632                        "name": "logger",
2633                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
2634                    },
2635                    {
2636                        "name": "something",
2637                        "url": "fuchsia-pkg://fuchsia.com/something/stable#meta/something.cm",
2638                    },
2639                    {
2640                        "name": "something-v2",
2641                        "url": "fuchsia-pkg://fuchsia.com/something/stable#meta/something-v2.cm",
2642                    },
2643                ],
2644                "offer": [
2645                    {
2646                        "protocol": "fuchsia.logger.LogSink",
2647                        "from": "#logger",
2648                        "to": "all",
2649                    },
2650                ],
2651            }),
2652            output = fdecl::Component {
2653                offers: Some(vec![
2654                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2655                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
2656                            name: "logger".into(),
2657                            collection: None,
2658                        })),
2659                        source_name: Some("fuchsia.logger.LogSink".into()),
2660                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2661                            name: "something".into(),
2662                            collection: None,
2663                        })),
2664                        target_name: Some("fuchsia.logger.LogSink".into()),
2665                        dependency_type: Some(fdecl::DependencyType::Strong),
2666                        availability: Some(fdecl::Availability::Required),
2667                        ..Default::default()
2668                    }),
2669                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2670                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
2671                            name: "logger".into(),
2672                            collection: None,
2673                        })),
2674                        source_name: Some("fuchsia.logger.LogSink".into()),
2675                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2676                            name: "something-v2".into(),
2677                            collection: None,
2678                        })),
2679                        target_name: Some("fuchsia.logger.LogSink".into()),
2680                        dependency_type: Some(fdecl::DependencyType::Strong),
2681                        availability: Some(fdecl::Availability::Required),
2682                        ..Default::default()
2683                    }),
2684                ]),
2685                children: Some(vec![
2686                    fdecl::Child {
2687                        name: Some("logger".into()),
2688                        url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".into()),
2689                        startup: Some(fdecl::StartupMode::Lazy),
2690                        ..Default::default()
2691                    },
2692                    fdecl::Child {
2693                        name: Some("something".into()),
2694                        url: Some(
2695                            "fuchsia-pkg://fuchsia.com/something/stable#meta/something.cm".into(),
2696                        ),
2697                        startup: Some(fdecl::StartupMode::Lazy),
2698                        ..Default::default()
2699                    },
2700                    fdecl::Child {
2701                        name: Some("something-v2".into()),
2702                        url: Some(
2703                            "fuchsia-pkg://fuchsia.com/something/stable#meta/something-v2.cm".into(),
2704                        ),
2705                        startup: Some(fdecl::StartupMode::Lazy),
2706                        ..Default::default()
2707                    },
2708                ]),
2709                ..default_component_decl()
2710            },
2711        },
2712
2713        test_compile_offer_multiple_protocols_to_single_array_syntax_and_all => {
2714            input = json!({
2715                "children": [
2716                    {
2717                        "name": "something",
2718                        "url": "fuchsia-pkg://fuchsia.com/something/stable#meta/something.cm",
2719                    },
2720                ],
2721                "offer": [
2722                    {
2723                        "protocol": ["fuchsia.logger.LogSink", "fuchsia.inspect.InspectSink",],
2724                        "from": "parent",
2725                        "to": "#something",
2726                    },
2727                    {
2728                        "protocol": "fuchsia.logger.LogSink",
2729                        "from": "parent",
2730                        "to": "all",
2731                    },
2732                ],
2733            }),
2734            output = fdecl::Component {
2735                offers: Some(vec![
2736                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2737                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2738                        source_name: Some("fuchsia.logger.LogSink".into()),
2739                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2740                            name: "something".into(),
2741                            collection: None,
2742                        })),
2743                        target_name: Some("fuchsia.logger.LogSink".into()),
2744                        dependency_type: Some(fdecl::DependencyType::Strong),
2745                        availability: Some(fdecl::Availability::Required),
2746                        ..Default::default()
2747                    }),
2748                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2749                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2750                        source_name: Some("fuchsia.inspect.InspectSink".into()),
2751                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2752                            name: "something".into(),
2753                            collection: None,
2754                        })),
2755                        target_name: Some("fuchsia.inspect.InspectSink".into()),
2756                        dependency_type: Some(fdecl::DependencyType::Strong),
2757                        availability: Some(fdecl::Availability::Required),
2758                        ..Default::default()
2759                    }),
2760                ]),
2761                children: Some(vec![
2762                    fdecl::Child {
2763                        name: Some("something".into()),
2764                        url: Some(
2765                            "fuchsia-pkg://fuchsia.com/something/stable#meta/something.cm".into(),
2766                        ),
2767                        startup: Some(fdecl::StartupMode::Lazy),
2768                        ..Default::default()
2769                    },
2770                ]),
2771                ..default_component_decl()
2772            },
2773        },
2774
2775        test_compile_offer_to_all_array_and_single => {
2776            input = json!({
2777                "children": [
2778                    {
2779                        "name": "something",
2780                        "url": "fuchsia-pkg://fuchsia.com/something/stable#meta/something.cm",
2781                    },
2782                ],
2783                "offer": [
2784                    {
2785                        "protocol": ["fuchsia.logger.LogSink", "fuchsia.inspect.InspectSink",],
2786                        "from": "parent",
2787                        "to": "all",
2788                    },
2789                    {
2790                        "protocol": "fuchsia.logger.LogSink",
2791                        "from": "parent",
2792                        "to": "#something",
2793                    },
2794                ],
2795            }),
2796            output = fdecl::Component {
2797                offers: Some(vec![
2798                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2799                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2800                        source_name: Some("fuchsia.logger.LogSink".into()),
2801                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2802                            name: "something".into(),
2803                            collection: None,
2804                        })),
2805                        target_name: Some("fuchsia.logger.LogSink".into()),
2806                        dependency_type: Some(fdecl::DependencyType::Strong),
2807                        availability: Some(fdecl::Availability::Required),
2808                        ..Default::default()
2809                    }),
2810                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2811                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2812                        source_name: Some("fuchsia.inspect.InspectSink".into()),
2813                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2814                            name: "something".into(),
2815                            collection: None,
2816                        })),
2817                        target_name: Some("fuchsia.inspect.InspectSink".into()),
2818                        dependency_type: Some(fdecl::DependencyType::Strong),
2819                        availability: Some(fdecl::Availability::Required),
2820                        ..Default::default()
2821                    }),
2822                ]),
2823                children: Some(vec![
2824                    fdecl::Child {
2825                        name: Some("something".into()),
2826                        url: Some(
2827                            "fuchsia-pkg://fuchsia.com/something/stable#meta/something.cm".into(),
2828                        ),
2829                        startup: Some(fdecl::StartupMode::Lazy),
2830                        ..Default::default()
2831                    },
2832                ]),
2833                ..default_component_decl()
2834            },
2835        },
2836
2837        test_compile_program => {
2838            input = json!({
2839                "program": {
2840                    "runner": "elf",
2841                    "binary": "bin/app",
2842                },
2843            }),
2844            output = fdecl::Component {
2845                program: Some(fdecl::Program {
2846                    runner: Some("elf".to_string()),
2847                    info: Some(fdata::Dictionary {
2848                        entries: Some(vec![fdata::DictionaryEntry {
2849                            key: "binary".to_string(),
2850                            value: Some(Box::new(fdata::DictionaryValue::Str("bin/app".to_string()))),
2851                        }]),
2852                        ..Default::default()
2853                    }),
2854                    ..Default::default()
2855                }),
2856                ..default_component_decl()
2857            },
2858        },
2859
2860        test_compile_program_with_use_runner => {
2861            input = json!({
2862                "program": {
2863                    "binary": "bin/app",
2864                },
2865                "use": [
2866                    { "runner": "elf", "from": "parent", },
2867                ],
2868            }),
2869            output = fdecl::Component {
2870                program: Some(fdecl::Program {
2871                    runner: None,
2872                    info: Some(fdata::Dictionary {
2873                        entries: Some(vec![fdata::DictionaryEntry {
2874                            key: "binary".to_string(),
2875                            value: Some(Box::new(fdata::DictionaryValue::Str("bin/app".to_string()))),
2876                        }]),
2877                        ..Default::default()
2878                    }),
2879                    ..Default::default()
2880                }),
2881                uses: Some(vec![
2882                    fdecl::Use::Runner (
2883                        fdecl::UseRunner {
2884                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2885                            source_name: Some("elf".to_string()),
2886                            ..Default::default()
2887                        }
2888                    ),
2889                ]),
2890                ..default_component_decl()
2891            },
2892        },
2893
2894        test_compile_program_with_nested_objects => {
2895            input = json!({
2896                "program": {
2897                    "runner": "elf",
2898                    "binary": "bin/app",
2899                    "one": {
2900                        "two": {
2901                            "three.four": {
2902                                "five": "six"
2903                            }
2904                        },
2905                    }
2906                },
2907            }),
2908            output = fdecl::Component {
2909                program: Some(fdecl::Program {
2910                    runner: Some("elf".to_string()),
2911                    info: Some(fdata::Dictionary {
2912                        entries: Some(vec![
2913                            fdata::DictionaryEntry {
2914                                key: "binary".to_string(),
2915                                value: Some(Box::new(fdata::DictionaryValue::Str("bin/app".to_string()))),
2916                            },
2917                            fdata::DictionaryEntry {
2918                                key: "one.two.three.four.five".to_string(),
2919                                value: Some(Box::new(fdata::DictionaryValue::Str("six".to_string()))),
2920                            },
2921                        ]),
2922                        ..Default::default()
2923                    }),
2924                    ..Default::default()
2925                }),
2926                ..default_component_decl()
2927            },
2928        },
2929
2930        test_compile_program_with_array_of_objects => {
2931            input = json!({
2932                "program": {
2933                    "runner": "elf",
2934                    "binary": "bin/app",
2935                    "networks": [
2936                        {
2937                            "endpoints": [
2938                                {
2939                                    "name": "device",
2940                                    "mac": "aa:bb:cc:dd:ee:ff"
2941                                },
2942                                {
2943                                    "name": "emu",
2944                                    "mac": "ff:ee:dd:cc:bb:aa"
2945                                },
2946                            ],
2947                            "name": "external_network"
2948                        }
2949                    ],
2950                },
2951            }),
2952            output = fdecl::Component {
2953                program: Some(fdecl::Program {
2954                    runner: Some("elf".to_string()),
2955                    info: Some(fdata::Dictionary {
2956                        entries: Some(vec![
2957                            fdata::DictionaryEntry {
2958                                key: "binary".to_string(),
2959                                value: Some(Box::new(fdata::DictionaryValue::Str("bin/app".to_string()))),
2960                            },
2961                            fdata::DictionaryEntry {
2962                                key: "networks".to_string(),
2963                                value: Some(Box::new(fdata::DictionaryValue::ObjVec(vec![
2964                                    fdata::Dictionary {
2965                                        entries: Some(vec![
2966                                            fdata::DictionaryEntry {
2967                                                key: "endpoints".to_string(),
2968                                                value: Some(Box::new(fdata::DictionaryValue::ObjVec(vec![
2969                                                    fdata::Dictionary {
2970                                                        entries: Some(vec![
2971                                                            fdata::DictionaryEntry {
2972                                                                key: "mac".to_string(),
2973                                                                value: Some(Box::new(fdata::DictionaryValue::Str("aa:bb:cc:dd:ee:ff".to_string()))),
2974                                                            },
2975                                                            fdata::DictionaryEntry {
2976                                                                key: "name".to_string(),
2977                                                                value: Some(Box::new(fdata::DictionaryValue::Str("device".to_string()))),
2978                                                            }
2979                                                        ]),
2980                                                        ..Default::default()
2981                                                    },
2982                                                    fdata::Dictionary {
2983                                                        entries: Some(vec![
2984                                                            fdata::DictionaryEntry {
2985                                                                key: "mac".to_string(),
2986                                                                value: Some(Box::new(fdata::DictionaryValue::Str("ff:ee:dd:cc:bb:aa".to_string()))),
2987                                                            },
2988                                                            fdata::DictionaryEntry {
2989                                                                key: "name".to_string(),
2990                                                                value: Some(Box::new(fdata::DictionaryValue::Str("emu".to_string()))),
2991                                                            }
2992                                                        ]),
2993                                                        ..Default::default()
2994                                                    },
2995                                                ])))
2996                                            },
2997                                            fdata::DictionaryEntry {
2998                                                key: "name".to_string(),
2999                                                value: Some(Box::new(fdata::DictionaryValue::Str("external_network".to_string()))),
3000                                            },
3001                                        ]),
3002                                        ..Default::default()
3003                                    }
3004                                ]))),
3005                            },
3006                        ]),
3007                        ..Default::default()
3008                    }),
3009                    ..Default::default()
3010                }),
3011                ..default_component_decl()
3012            },
3013        },
3014
3015        test_compile_use => {
3016            features = FeatureSet::from(vec![Feature::UseDictionaries]),
3017            input = json!({
3018                "use": [
3019                    {
3020                        "protocol": "LegacyCoolFonts",
3021                        "path": "/svc/fuchsia.fonts.LegacyProvider",
3022                        "availability": "optional",
3023                    },
3024                    {
3025                        "protocol": "LegacyCoolFonts",
3026                        "numbered_handle": 0xab,
3027                    },
3028                    { "protocol": "fuchsia.sys2.LegacyRealm", "from": "framework" },
3029                    { "protocol": "fuchsia.sys2.StorageAdmin", "from": "#data-storage" },
3030                    { "protocol": "fuchsia.sys2.DebugProto", "from": "debug" },
3031                    { "protocol": "fuchsia.sys2.DictionaryProto", "from": "#logger/in/dict" },
3032                    { "protocol": "fuchsia.sys2.Echo", "from": "self", "availability": "transitional" },
3033                    { "service": "fuchsia.sys2.EchoService", "from": "parent/dict", },
3034                    { "directory": "assets", "rights" : ["read_bytes"], "path": "/data/assets" },
3035                    {
3036                        "directory": "config",
3037                        "path": "/data/config",
3038                        "from": "parent",
3039                        "rights": ["read_bytes"],
3040                        "subdir": "fonts",
3041                    },
3042                    { "storage": "hippos", "path": "/hippos" },
3043                    { "storage": "cache", "path": "/tmp" },
3044                    {
3045                        "event_stream": "bar_stream",
3046                    },
3047                    {
3048                        "event_stream": ["foobar", "stream"],
3049                        "scope": ["#logger", "#modular"],
3050                        "path": "/event_stream/another",
3051                    },
3052                    { "runner": "usain", "from": "parent", },
3053                    {
3054                        "dictionary": "toolbox",
3055                        "path": "/svc",
3056                    },
3057                ],
3058                "capabilities": [
3059                    { "protocol": "fuchsia.sys2.Echo" },
3060                    {
3061                        "config": "fuchsia.config.Config",
3062                        "type": "bool",
3063                        "value": true,
3064                    },
3065                    {
3066                        "storage": "data-storage",
3067                        "from": "parent",
3068                        "backing_dir": "minfs",
3069                        "storage_id": "static_instance_id_or_moniker",
3070                    }
3071                ],
3072                "children": [
3073                    {
3074                        "name": "logger",
3075                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
3076                        "environment": "#env_one"
3077                    }
3078                ],
3079                "collections": [
3080                    {
3081                        "name": "modular",
3082                        "durability": "transient",
3083                    },
3084                ],
3085                "environments": [
3086                    {
3087                        "name": "env_one",
3088                        "extends": "realm",
3089                    }
3090                ]
3091            }),
3092            output = fdecl::Component {
3093                uses: Some(vec![
3094                    fdecl::Use::Protocol (
3095                        fdecl::UseProtocol {
3096                            dependency_type: Some(fdecl::DependencyType::Strong),
3097                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3098                            source_name: Some("LegacyCoolFonts".to_string()),
3099                            target_path: Some("/svc/fuchsia.fonts.LegacyProvider".to_string()),
3100                            availability: Some(fdecl::Availability::Optional),
3101                            ..Default::default()
3102                        }
3103                    ),
3104                    fdecl::Use::Protocol (
3105                        fdecl::UseProtocol {
3106                            dependency_type: Some(fdecl::DependencyType::Strong),
3107                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3108                            source_name: Some("LegacyCoolFonts".to_string()),
3109                            numbered_handle: Some(0xab),
3110                            availability: Some(fdecl::Availability::Required),
3111                            ..Default::default()
3112                        }
3113                    ),
3114                    fdecl::Use::Protocol (
3115                        fdecl::UseProtocol {
3116                            dependency_type: Some(fdecl::DependencyType::Strong),
3117                            source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef {})),
3118                            source_name: Some("fuchsia.sys2.LegacyRealm".to_string()),
3119                            target_path: Some("/svc/fuchsia.sys2.LegacyRealm".to_string()),
3120                            availability: Some(fdecl::Availability::Required),
3121                            ..Default::default()
3122                        }
3123                    ),
3124                    fdecl::Use::Protocol (
3125                        fdecl::UseProtocol {
3126                            dependency_type: Some(fdecl::DependencyType::Strong),
3127                            source: Some(fdecl::Ref::Capability(fdecl::CapabilityRef { name: "data-storage".to_string() })),
3128                            source_name: Some("fuchsia.sys2.StorageAdmin".to_string()),
3129                            target_path: Some("/svc/fuchsia.sys2.StorageAdmin".to_string()),
3130                            availability: Some(fdecl::Availability::Required),
3131                            ..Default::default()
3132                        }
3133                    ),
3134                    fdecl::Use::Protocol (
3135                        fdecl::UseProtocol {
3136                            dependency_type: Some(fdecl::DependencyType::Strong),
3137                            source: Some(fdecl::Ref::Debug(fdecl::DebugRef {})),
3138                            source_name: Some("fuchsia.sys2.DebugProto".to_string()),
3139                            target_path: Some("/svc/fuchsia.sys2.DebugProto".to_string()),
3140                            availability: Some(fdecl::Availability::Required),
3141                            ..Default::default()
3142                        }
3143                    ),
3144                    fdecl::Use::Protocol (
3145                        fdecl::UseProtocol {
3146                            dependency_type: Some(fdecl::DependencyType::Strong),
3147                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3148                                name: "logger".into(),
3149                                collection: None,
3150                            })),
3151                            source_dictionary: Some("in/dict".into()),
3152                            source_name: Some("fuchsia.sys2.DictionaryProto".to_string()),
3153                            target_path: Some("/svc/fuchsia.sys2.DictionaryProto".to_string()),
3154                            availability: Some(fdecl::Availability::Required),
3155                            ..Default::default()
3156                        }
3157                    ),
3158                    fdecl::Use::Protocol (
3159                        fdecl::UseProtocol {
3160                            dependency_type: Some(fdecl::DependencyType::Strong),
3161                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
3162                            source_name: Some("fuchsia.sys2.Echo".to_string()),
3163                            target_path: Some("/svc/fuchsia.sys2.Echo".to_string()),
3164                            availability: Some(fdecl::Availability::Transitional),
3165                            ..Default::default()
3166                        }
3167                    ),
3168                    fdecl::Use::Service (
3169                        fdecl::UseService {
3170                            dependency_type: Some(fdecl::DependencyType::Strong),
3171                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3172                            source_dictionary: Some("dict".into()),
3173                            source_name: Some("fuchsia.sys2.EchoService".to_string()),
3174                            target_path: Some("/svc/fuchsia.sys2.EchoService".to_string()),
3175                            availability: Some(fdecl::Availability::Required),
3176                            ..Default::default()
3177                        }
3178                    ),
3179                    fdecl::Use::Directory (
3180                        fdecl::UseDirectory {
3181                            dependency_type: Some(fdecl::DependencyType::Strong),
3182                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3183                            source_name: Some("assets".to_string()),
3184                            target_path: Some("/data/assets".to_string()),
3185                            rights: Some(fio::Operations::READ_BYTES),
3186                            subdir: None,
3187                            availability: Some(fdecl::Availability::Required),
3188                            ..Default::default()
3189                        }
3190                    ),
3191                    fdecl::Use::Directory (
3192                        fdecl::UseDirectory {
3193                            dependency_type: Some(fdecl::DependencyType::Strong),
3194                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3195                            source_name: Some("config".to_string()),
3196                            target_path: Some("/data/config".to_string()),
3197                            rights: Some(fio::Operations::READ_BYTES),
3198                            subdir: Some("fonts".to_string()),
3199                            availability: Some(fdecl::Availability::Required),
3200                            ..Default::default()
3201                        }
3202                    ),
3203                    fdecl::Use::Storage (
3204                        fdecl::UseStorage {
3205                            source_name: Some("hippos".to_string()),
3206                            target_path: Some("/hippos".to_string()),
3207                            availability: Some(fdecl::Availability::Required),
3208                            ..Default::default()
3209                        }
3210                    ),
3211                    fdecl::Use::Storage (
3212                        fdecl::UseStorage {
3213                            source_name: Some("cache".to_string()),
3214                            target_path: Some("/tmp".to_string()),
3215                            availability: Some(fdecl::Availability::Required),
3216                            ..Default::default()
3217                        }
3218                    ),
3219                    fdecl::Use::EventStream(fdecl::UseEventStream {
3220                        source_name: Some("bar_stream".to_string()),
3221                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
3222                        target_path: Some("/svc/fuchsia.component.EventStream".to_string()),
3223                        availability: Some(fdecl::Availability::Required),
3224                        ..Default::default()
3225                    }),
3226                    fdecl::Use::EventStream(fdecl::UseEventStream {
3227                        source_name: Some("foobar".to_string()),
3228                        scope: Some(vec![fdecl::Ref::Child(fdecl::ChildRef{name:"logger".to_string(), collection: None}), fdecl::Ref::Collection(fdecl::CollectionRef{name:"modular".to_string()})]),
3229                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
3230                        target_path: Some("/event_stream/another".to_string()),
3231                        availability: Some(fdecl::Availability::Required),
3232                        ..Default::default()
3233                    }),
3234                    fdecl::Use::EventStream(fdecl::UseEventStream {
3235                        source_name: Some("stream".to_string()),
3236                        scope: Some(vec![fdecl::Ref::Child(fdecl::ChildRef{name:"logger".to_string(), collection: None}), fdecl::Ref::Collection(fdecl::CollectionRef{name:"modular".to_string()})]),
3237                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
3238                        target_path: Some("/event_stream/another".to_string()),
3239                        availability: Some(fdecl::Availability::Required),
3240                        ..Default::default()
3241                    }),
3242                    fdecl::Use::Runner(fdecl::UseRunner {
3243                        source_name: Some("usain".to_string()),
3244                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
3245                        ..Default::default()
3246                    }),
3247                    fdecl::Use::Dictionary(fdecl::UseDictionary {
3248                        source_name: Some("toolbox".to_string()),
3249                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
3250                        target_path: Some("/svc".to_string()),
3251                        dependency_type: Some(fdecl::DependencyType::Strong),
3252                        availability: Some(fdecl::Availability::Required),
3253                        ..Default::default()
3254                    }),
3255                ]),
3256                collections: Some(vec![
3257                    fdecl::Collection{
3258                        name:Some("modular".to_string()),
3259                        durability:Some(fdecl::Durability::Transient),
3260                        ..Default::default()
3261                    },
3262                ]),
3263                capabilities: Some(vec![
3264                    fdecl::Capability::Protocol(fdecl::Protocol {
3265                        name: Some("fuchsia.sys2.Echo".to_string()),
3266                        source_path: Some("/svc/fuchsia.sys2.Echo".to_string()),
3267                        ..Default::default()
3268                    }),
3269                    fdecl::Capability::Config(fdecl::Configuration {
3270                        name: Some("fuchsia.config.Config".to_string()),
3271                        value: Some(fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Bool(true))),
3272                        ..Default::default()
3273                    }),
3274                    fdecl::Capability::Storage(fdecl::Storage {
3275                        name: Some("data-storage".to_string()),
3276                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3277                        backing_dir: Some("minfs".to_string()),
3278                        subdir: None,
3279                        storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker),
3280                        ..Default::default()
3281                    }),
3282                ]),
3283                children: Some(vec![
3284                    fdecl::Child{
3285                        name:Some("logger".to_string()),
3286                        url:Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
3287                        startup:Some(fdecl::StartupMode::Lazy),
3288                        environment: Some("env_one".to_string()),
3289                        ..Default::default()
3290                    }
3291                ]),
3292                environments: Some(vec![
3293                    fdecl::Environment {
3294                        name: Some("env_one".to_string()),
3295                        extends: Some(fdecl::EnvironmentExtends::Realm),
3296                        ..Default::default()
3297                    },
3298                ]),
3299                config: None,
3300                ..default_component_decl()
3301            },
3302        },
3303
3304        test_compile_expose => {
3305            input = json!({
3306                "expose": [
3307                    {
3308                        "protocol": "fuchsia.logger.Log",
3309                        "from": "#logger",
3310                        "as": "fuchsia.logger.LegacyLog",
3311                        "to": "parent"
3312                    },
3313                    {
3314                        "protocol": [ "A", "B" ],
3315                        "from": "self",
3316                        "to": "parent"
3317                    },
3318                    {
3319                        "protocol": "C",
3320                        "from": "#data-storage",
3321                    },
3322                    {
3323                        "protocol": "D",
3324                        "from": "#logger/in/dict",
3325                        "as": "E",
3326                    },
3327                    {
3328                        "service": "F",
3329                        "from": "#logger/in/dict",
3330                    },
3331                    {
3332                        "service": "svc",
3333                        "from": [ "#logger", "#coll", "self" ],
3334                    },
3335                    {
3336                        "directory": "blob",
3337                        "from": "self",
3338                        "to": "framework",
3339                        "rights": ["r*"],
3340                    },
3341                    {
3342                        "directory": [ "blob2", "blob3" ],
3343                        "from": "#logger",
3344                        "to": "parent",
3345                    },
3346                    { "directory": "hub", "from": "framework" },
3347                    { "runner": "web", "from": "#logger", "to": "parent", "as": "web-rename" },
3348                    { "runner": [ "runner_a", "runner_b" ], "from": "#logger" },
3349                    { "resolver": "my_resolver", "from": "#logger", "to": "parent", "as": "pkg_resolver" },
3350                    { "resolver": [ "resolver_a", "resolver_b" ], "from": "#logger" },
3351                    { "dictionary": [ "dictionary_a", "dictionary_b" ], "from": "#logger" },
3352                ],
3353                "capabilities": [
3354                    { "protocol": "A" },
3355                    { "protocol": "B" },
3356                    { "service": "svc" },
3357                    {
3358                        "directory": "blob",
3359                        "path": "/volumes/blobfs/blob",
3360                        "rights": ["r*"],
3361                    },
3362                    {
3363                        "runner": "web",
3364                        "path": "/svc/fuchsia.component.ComponentRunner",
3365                    },
3366                    {
3367                        "storage": "data-storage",
3368                        "from": "parent",
3369                        "backing_dir": "minfs",
3370                        "storage_id": "static_instance_id_or_moniker",
3371                    },
3372                ],
3373                "children": [
3374                    {
3375                        "name": "logger",
3376                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
3377                    },
3378                ],
3379                "collections": [
3380                    {
3381                        "name": "coll",
3382                        "durability": "transient",
3383                    },
3384                ],
3385            }),
3386            output = fdecl::Component {
3387                exposes: Some(vec![
3388                    fdecl::Expose::Protocol (
3389                        fdecl::ExposeProtocol {
3390                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3391                                name: "logger".to_string(),
3392                                collection: None,
3393                            })),
3394                            source_name: Some("fuchsia.logger.Log".to_string()),
3395                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3396                            target_name: Some("fuchsia.logger.LegacyLog".to_string()),
3397                            availability: Some(fdecl::Availability::Required),
3398                            ..Default::default()
3399                        }
3400                    ),
3401                    fdecl::Expose::Protocol (
3402                        fdecl::ExposeProtocol {
3403                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
3404                            source_name: Some("A".to_string()),
3405                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3406                            target_name: Some("A".to_string()),
3407                            availability: Some(fdecl::Availability::Required),
3408                            ..Default::default()
3409                        }
3410                    ),
3411                    fdecl::Expose::Protocol (
3412                        fdecl::ExposeProtocol {
3413                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
3414                            source_name: Some("B".to_string()),
3415                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3416                            target_name: Some("B".to_string()),
3417                            availability: Some(fdecl::Availability::Required),
3418                            ..Default::default()
3419                        }
3420                    ),
3421                    fdecl::Expose::Protocol (
3422                        fdecl::ExposeProtocol {
3423                            source: Some(fdecl::Ref::Capability(fdecl::CapabilityRef {
3424                                name: "data-storage".to_string(),
3425                            })),
3426                            source_name: Some("C".to_string()),
3427                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3428                            target_name: Some("C".to_string()),
3429                            availability: Some(fdecl::Availability::Required),
3430                            ..Default::default()
3431                        }
3432                    ),
3433                    fdecl::Expose::Protocol (
3434                        fdecl::ExposeProtocol {
3435                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3436                                name: "logger".to_string(),
3437                                collection: None,
3438                            })),
3439                            source_dictionary: Some("in/dict".into()),
3440                            source_name: Some("D".to_string()),
3441                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3442                            target_name: Some("E".to_string()),
3443                            availability: Some(fdecl::Availability::Required),
3444                            ..Default::default()
3445                        }
3446                    ),
3447                    fdecl::Expose::Service (
3448                        fdecl::ExposeService {
3449                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3450                                name: "logger".into(),
3451                                collection: None,
3452                            })),
3453                            source_name: Some("F".into()),
3454                            source_dictionary: Some("in/dict".into()),
3455                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3456                            target_name: Some("F".into()),
3457                            availability: Some(fdecl::Availability::Required),
3458                            ..Default::default()
3459                        }
3460                    ),
3461                    fdecl::Expose::Service (
3462                        fdecl::ExposeService {
3463                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3464                                name: "logger".into(),
3465                                collection: None,
3466                            })),
3467                            source_name: Some("svc".into()),
3468                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3469                            target_name: Some("svc".into()),
3470                            availability: Some(fdecl::Availability::Required),
3471                            ..Default::default()
3472                        }
3473                    ),
3474                    fdecl::Expose::Service (
3475                        fdecl::ExposeService {
3476                            source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
3477                                name: "coll".into(),
3478                            })),
3479                            source_name: Some("svc".into()),
3480                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3481                            target_name: Some("svc".into()),
3482                            availability: Some(fdecl::Availability::Required),
3483                            ..Default::default()
3484                        }
3485                    ),
3486                    fdecl::Expose::Service (
3487                        fdecl::ExposeService {
3488                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
3489                            source_name: Some("svc".into()),
3490                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3491                            target_name: Some("svc".into()),
3492                            availability: Some(fdecl::Availability::Required),
3493                            ..Default::default()
3494                        }
3495                    ),
3496                    fdecl::Expose::Directory (
3497                        fdecl::ExposeDirectory {
3498                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
3499                            source_name: Some("blob".to_string()),
3500                            target: Some(fdecl::Ref::Framework(fdecl::FrameworkRef {})),
3501                            target_name: Some("blob".to_string()),
3502                            rights: Some(
3503                                fio::Operations::CONNECT | fio::Operations::ENUMERATE |
3504                                fio::Operations::TRAVERSE | fio::Operations::READ_BYTES |
3505                                fio::Operations::GET_ATTRIBUTES
3506                            ),
3507                            subdir: None,
3508                            availability: Some(fdecl::Availability::Required),
3509                            ..Default::default()
3510                        }
3511                    ),
3512                    fdecl::Expose::Directory (
3513                        fdecl::ExposeDirectory {
3514                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3515                                name: "logger".to_string(),
3516                                collection: None,
3517                            })),
3518                            source_name: Some("blob2".to_string()),
3519                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3520                            target_name: Some("blob2".to_string()),
3521                            rights: None,
3522                            subdir: None,
3523                            availability: Some(fdecl::Availability::Required),
3524                            ..Default::default()
3525                        }
3526                    ),
3527                    fdecl::Expose::Directory (
3528                        fdecl::ExposeDirectory {
3529                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3530                                name: "logger".to_string(),
3531                                collection: None,
3532                            })),
3533                            source_name: Some("blob3".to_string()),
3534                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3535                            target_name: Some("blob3".to_string()),
3536                            rights: None,
3537                            subdir: None,
3538                            availability: Some(fdecl::Availability::Required),
3539                            ..Default::default()
3540                        }
3541                    ),
3542                    fdecl::Expose::Directory (
3543                        fdecl::ExposeDirectory {
3544                            source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef {})),
3545                            source_name: Some("hub".to_string()),
3546                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3547                            target_name: Some("hub".to_string()),
3548                            rights: None,
3549                            subdir: None,
3550                            availability: Some(fdecl::Availability::Required),
3551                            ..Default::default()
3552                        }
3553                    ),
3554                    fdecl::Expose::Runner (
3555                        fdecl::ExposeRunner {
3556                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3557                                name: "logger".to_string(),
3558                                collection: None,
3559                            })),
3560                            source_name: Some("web".to_string()),
3561                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3562                            target_name: Some("web-rename".to_string()),
3563                            ..Default::default()
3564                        }
3565                    ),
3566                    fdecl::Expose::Runner (
3567                        fdecl::ExposeRunner {
3568                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3569                                name: "logger".to_string(),
3570                                collection: None,
3571                            })),
3572                            source_name: Some("runner_a".to_string()),
3573                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3574                            target_name: Some("runner_a".to_string()),
3575                            ..Default::default()
3576                        }
3577                    ),
3578                    fdecl::Expose::Runner (
3579                        fdecl::ExposeRunner {
3580                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3581                                name: "logger".to_string(),
3582                                collection: None,
3583                            })),
3584                            source_name: Some("runner_b".to_string()),
3585                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3586                            target_name: Some("runner_b".to_string()),
3587                            ..Default::default()
3588                        }
3589                    ),
3590                    fdecl::Expose::Resolver (
3591                        fdecl::ExposeResolver {
3592                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3593                                name: "logger".to_string(),
3594                                collection: None,
3595                            })),
3596                            source_name: Some("my_resolver".to_string()),
3597                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3598                            target_name: Some("pkg_resolver".to_string()),
3599                            ..Default::default()
3600                        }
3601                    ),
3602                    fdecl::Expose::Resolver (
3603                        fdecl::ExposeResolver {
3604                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3605                                name: "logger".to_string(),
3606                                collection: None,
3607                            })),
3608                            source_name: Some("resolver_a".to_string()),
3609                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3610                            target_name: Some("resolver_a".to_string()),
3611                            ..Default::default()
3612                        }
3613                    ),
3614                    fdecl::Expose::Resolver (
3615                        fdecl::ExposeResolver {
3616                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3617                                name: "logger".to_string(),
3618                                collection: None,
3619                            })),
3620                            source_name: Some("resolver_b".to_string()),
3621                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3622                            target_name: Some("resolver_b".to_string()),
3623                            ..Default::default()
3624                        }
3625                    ),
3626                   fdecl::Expose::Dictionary (
3627                        fdecl::ExposeDictionary {
3628                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3629                                name: "logger".to_string(),
3630                                collection: None,
3631                            })),
3632                            source_name: Some("dictionary_a".to_string()),
3633                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3634                            target_name: Some("dictionary_a".to_string()),
3635                            availability: Some(fdecl::Availability::Required),
3636                            ..Default::default()
3637                        }
3638                    ),
3639                    fdecl::Expose::Dictionary (
3640                        fdecl::ExposeDictionary {
3641                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3642                                name: "logger".to_string(),
3643                                collection: None,
3644                            })),
3645                            source_name: Some("dictionary_b".to_string()),
3646                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3647                            target_name: Some("dictionary_b".to_string()),
3648                            availability: Some(fdecl::Availability::Required),
3649                            ..Default::default()
3650                        }
3651                    ),
3652                ]),
3653                offers: None,
3654                capabilities: Some(vec![
3655                    fdecl::Capability::Protocol (
3656                        fdecl::Protocol {
3657                            name: Some("A".to_string()),
3658                            source_path: Some("/svc/A".to_string()),
3659                            ..Default::default()
3660                        }
3661                    ),
3662                    fdecl::Capability::Protocol (
3663                        fdecl::Protocol {
3664                            name: Some("B".to_string()),
3665                            source_path: Some("/svc/B".to_string()),
3666                            ..Default::default()
3667                        }
3668                    ),
3669                    fdecl::Capability::Service (
3670                        fdecl::Service {
3671                            name: Some("svc".to_string()),
3672                            source_path: Some("/svc/svc".to_string()),
3673                            ..Default::default()
3674                        }
3675                    ),
3676                    fdecl::Capability::Directory (
3677                        fdecl::Directory {
3678                            name: Some("blob".to_string()),
3679                            source_path: Some("/volumes/blobfs/blob".to_string()),
3680                            rights: Some(fio::Operations::CONNECT | fio::Operations::ENUMERATE |
3681                                fio::Operations::TRAVERSE | fio::Operations::READ_BYTES |
3682                                fio::Operations::GET_ATTRIBUTES
3683                            ),
3684                            ..Default::default()
3685                        }
3686                    ),
3687                    fdecl::Capability::Runner (
3688                        fdecl::Runner {
3689                            name: Some("web".to_string()),
3690                            source_path: Some("/svc/fuchsia.component.ComponentRunner".to_string()),
3691                            ..Default::default()
3692                        }
3693                    ),
3694                    fdecl::Capability::Storage(fdecl::Storage {
3695                        name: Some("data-storage".to_string()),
3696                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3697                        backing_dir: Some("minfs".to_string()),
3698                        subdir: None,
3699                        storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker),
3700                        ..Default::default()
3701                    }),
3702                ]),
3703                children: Some(vec![
3704                    fdecl::Child {
3705                        name: Some("logger".to_string()),
3706                        url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
3707                        startup: Some(fdecl::StartupMode::Lazy),
3708                        ..Default::default()
3709                    }
3710                ]),
3711                collections: Some(vec![
3712                    fdecl::Collection {
3713                        name: Some("coll".to_string()),
3714                        durability: Some(fdecl::Durability::Transient),
3715                        ..Default::default()
3716                    }
3717                ]),
3718                ..default_component_decl()
3719            },
3720        },
3721
3722        test_compile_expose_other_availability => {
3723            input = json!({
3724                "expose": [
3725                    {
3726                        "protocol": "fuchsia.logger.Log",
3727                        "from": "#logger",
3728                        "as": "fuchsia.logger.LegacyLog_default",
3729                        "to": "parent"
3730                    },
3731                    {
3732                        "protocol": "fuchsia.logger.Log",
3733                        "from": "#logger",
3734                        "as": "fuchsia.logger.LegacyLog_required",
3735                        "to": "parent",
3736                        "availability": "required"
3737                    },
3738                    {
3739                        "protocol": "fuchsia.logger.Log",
3740                        "from": "#logger",
3741                        "as": "fuchsia.logger.LegacyLog_optional",
3742                        "to": "parent",
3743                        "availability": "optional"
3744                    },
3745                    {
3746                        "protocol": "fuchsia.logger.Log",
3747                        "from": "#logger",
3748                        "as": "fuchsia.logger.LegacyLog_same_as_target",
3749                        "to": "parent",
3750                        "availability": "same_as_target"
3751                    },
3752                    {
3753                        "protocol": "fuchsia.logger.Log",
3754                        "from": "#logger",
3755                        "as": "fuchsia.logger.LegacyLog_transitional",
3756                        "to": "parent",
3757                        "availability": "transitional"
3758                    },
3759                ],
3760                "children": [
3761                    {
3762                        "name": "logger",
3763                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
3764                    },
3765                ],
3766            }),
3767            output = fdecl::Component {
3768                exposes: Some(vec![
3769                    fdecl::Expose::Protocol (
3770                        fdecl::ExposeProtocol {
3771                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3772                                name: "logger".to_string(),
3773                                collection: None,
3774                            })),
3775                            source_name: Some("fuchsia.logger.Log".to_string()),
3776                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3777                            target_name: Some("fuchsia.logger.LegacyLog_default".to_string()),
3778                            availability: Some(fdecl::Availability::Required),
3779                            ..Default::default()
3780                        }
3781                    ),
3782                    fdecl::Expose::Protocol (
3783                        fdecl::ExposeProtocol {
3784                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3785                                name: "logger".to_string(),
3786                                collection: None,
3787                            })),
3788                            source_name: Some("fuchsia.logger.Log".to_string()),
3789                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3790                            target_name: Some("fuchsia.logger.LegacyLog_required".to_string()),
3791                            availability: Some(fdecl::Availability::Required),
3792                            ..Default::default()
3793                        }
3794                    ),
3795                    fdecl::Expose::Protocol (
3796                        fdecl::ExposeProtocol {
3797                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3798                                name: "logger".to_string(),
3799                                collection: None,
3800                            })),
3801                            source_name: Some("fuchsia.logger.Log".to_string()),
3802                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3803                            target_name: Some("fuchsia.logger.LegacyLog_optional".to_string()),
3804                            availability: Some(fdecl::Availability::Optional),
3805                            ..Default::default()
3806                        }
3807                    ),
3808                    fdecl::Expose::Protocol (
3809                        fdecl::ExposeProtocol {
3810                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3811                                name: "logger".to_string(),
3812                                collection: None,
3813                            })),
3814                            source_name: Some("fuchsia.logger.Log".to_string()),
3815                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3816                            target_name: Some("fuchsia.logger.LegacyLog_same_as_target".to_string()),
3817                            availability: Some(fdecl::Availability::SameAsTarget),
3818                            ..Default::default()
3819                        }
3820                    ),
3821                    fdecl::Expose::Protocol (
3822                        fdecl::ExposeProtocol {
3823                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3824                                name: "logger".to_string(),
3825                                collection: None,
3826                            })),
3827                            source_name: Some("fuchsia.logger.Log".to_string()),
3828                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3829                            target_name: Some("fuchsia.logger.LegacyLog_transitional".to_string()),
3830                            availability: Some(fdecl::Availability::Transitional),
3831                            ..Default::default()
3832                        }
3833                    ),
3834                ]),
3835                offers: None,
3836                capabilities: None,
3837                children: Some(vec![
3838                    fdecl::Child {
3839                        name: Some("logger".to_string()),
3840                        url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
3841                        startup: Some(fdecl::StartupMode::Lazy),
3842                        environment: None,
3843                        on_terminate: None,
3844                        ..Default::default()
3845                    }
3846                ]),
3847                ..default_component_decl()
3848            },
3849        },
3850
3851        test_compile_expose_source_availability_unknown => {
3852            input = json!({
3853                "expose": [
3854                    {
3855                        "protocol": "fuchsia.logger.Log",
3856                        "from": "#non-existent",
3857                        "as": "fuchsia.logger.LegacyLog_non_existent",
3858                        "availability": "optional",
3859                        "source_availability": "unknown"
3860                    },
3861                    {
3862                        "protocol": "fuchsia.logger.Log",
3863                        "from": "#non-existent/dict",
3864                        "as": "fuchsia.logger.LegacyLog_non_existent2",
3865                        "availability": "optional",
3866                        "source_availability": "unknown"
3867                    },
3868                    {
3869                        "protocol": "fuchsia.logger.Log",
3870                        "from": "#logger",
3871                        "as": "fuchsia.logger.LegacyLog_child_exist",
3872                        "availability": "optional",
3873                        "source_availability": "unknown"
3874                    },
3875                ],
3876                "children": [
3877                    {
3878                        "name": "logger",
3879                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
3880                    },
3881                ],
3882            }),
3883            output = fdecl::Component {
3884                exposes: Some(vec![
3885                    fdecl::Expose::Protocol (
3886                        fdecl::ExposeProtocol {
3887                            source: Some(fdecl::Ref::VoidType(fdecl::VoidRef { })),
3888                            source_name: Some("fuchsia.logger.Log".to_string()),
3889                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3890                            target_name: Some("fuchsia.logger.LegacyLog_non_existent".to_string()),
3891                            availability: Some(fdecl::Availability::Optional),
3892                            ..Default::default()
3893                        }
3894                    ),
3895                    fdecl::Expose::Protocol (
3896                        fdecl::ExposeProtocol {
3897                            source: Some(fdecl::Ref::VoidType(fdecl::VoidRef { })),
3898                            source_name: Some("fuchsia.logger.Log".to_string()),
3899                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3900                            target_name: Some("fuchsia.logger.LegacyLog_non_existent2".to_string()),
3901                            availability: Some(fdecl::Availability::Optional),
3902                            ..Default::default()
3903                        }
3904                    ),
3905                    fdecl::Expose::Protocol (
3906                        fdecl::ExposeProtocol {
3907                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3908                                name: "logger".to_string(),
3909                                collection: None,
3910                            })),
3911                            source_name: Some("fuchsia.logger.Log".to_string()),
3912                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3913                            target_name: Some("fuchsia.logger.LegacyLog_child_exist".to_string()),
3914                            availability: Some(fdecl::Availability::Optional),
3915                            ..Default::default()
3916                        }
3917                    ),
3918                ]),
3919                children: Some(vec![
3920                    fdecl::Child {
3921                        name: Some("logger".to_string()),
3922                        url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
3923                        startup: Some(fdecl::StartupMode::Lazy),
3924                        ..Default::default()
3925                    }
3926                ]),
3927                ..default_component_decl()
3928            },
3929        },
3930
3931        test_compile_offer_target_availability_unknown => {
3932            input = json!({
3933                "offer": [
3934                    {
3935                        "protocol": "fuchsia.logger.Log",
3936                        "from": "#logger",
3937                        "to": "#non-existent",
3938                        "target_availability": "unknown",
3939                    },
3940                    {
3941                        "protocol": "fuchsia.logger.Log",
3942                        "from": "#logger",
3943                        "to": "self/non-existent-dict",
3944                        "target_availability": "unknown",
3945                    },
3946                ],
3947                "children": [
3948                    {
3949                        "name": "logger",
3950                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
3951                    },
3952                ],
3953            }),
3954            output = fdecl::Component {
3955                offers: Some(vec![]),
3956                children: Some(vec![
3957                    fdecl::Child {
3958                        name: Some("logger".to_string()),
3959                        url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
3960                        startup: Some(fdecl::StartupMode::Lazy),
3961                        ..Default::default()
3962                    },
3963                ]),
3964                ..default_component_decl()
3965            },
3966        },
3967
3968        test_compile_offer_source_availability_unknown => {
3969            input = json!({
3970                "offer": [
3971                    {
3972                        "protocol": "fuchsia.logger.Log",
3973                        "from": "#non-existent",
3974                        "as": "fuchsia.logger.LegacyLog_non_existent",
3975                        "to": "#target",
3976                        "availability": "optional",
3977                        "source_availability": "unknown"
3978                    },
3979                    {
3980                        "protocol": "fuchsia.logger.Log",
3981                        "from": "#non-existent/dict",
3982                        "as": "fuchsia.logger.LegacyLog_non_existent2",
3983                        "to": "#target",
3984                        "availability": "optional",
3985                        "source_availability": "unknown"
3986                    },
3987                    {
3988                        "protocol": "fuchsia.logger.Log",
3989                        "from": "#logger",
3990                        "as": "fuchsia.logger.LegacyLog_child_exist",
3991                        "to": "#target",
3992                        "availability": "optional",
3993                        "source_availability": "unknown"
3994                    },
3995                ],
3996                "children": [
3997                    {
3998                        "name": "logger",
3999                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
4000                    },
4001                    {
4002                        "name": "target",
4003                        "url": "#meta/target.cm"
4004                    },
4005                ],
4006            }),
4007            output = fdecl::Component {
4008                offers: Some(vec![
4009                    fdecl::Offer::Protocol (
4010                        fdecl::OfferProtocol {
4011                            source: Some(fdecl::Ref::VoidType(fdecl::VoidRef { })),
4012                            source_name: Some("fuchsia.logger.Log".to_string()),
4013                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4014                                name: "target".to_string(),
4015                                collection: None,
4016                            })),
4017                            target_name: Some("fuchsia.logger.LegacyLog_non_existent".to_string()),
4018                            dependency_type: Some(fdecl::DependencyType::Strong),
4019                            availability: Some(fdecl::Availability::Optional),
4020                            ..Default::default()
4021                        }
4022                    ),
4023                    fdecl::Offer::Protocol (
4024                        fdecl::OfferProtocol {
4025                            source: Some(fdecl::Ref::VoidType(fdecl::VoidRef { })),
4026                            source_name: Some("fuchsia.logger.Log".to_string()),
4027                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4028                                name: "target".to_string(),
4029                                collection: None,
4030                            })),
4031                            target_name: Some("fuchsia.logger.LegacyLog_non_existent2".to_string()),
4032                            dependency_type: Some(fdecl::DependencyType::Strong),
4033                            availability: Some(fdecl::Availability::Optional),
4034                            ..Default::default()
4035                        }
4036                    ),
4037                    fdecl::Offer::Protocol (
4038                        fdecl::OfferProtocol {
4039                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
4040                                name: "logger".to_string(),
4041                                collection: None,
4042                            })),
4043                            source_name: Some("fuchsia.logger.Log".to_string()),
4044                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4045                                name: "target".to_string(),
4046                                collection: None,
4047                            })),
4048                            target_name: Some("fuchsia.logger.LegacyLog_child_exist".to_string()),
4049                            dependency_type: Some(fdecl::DependencyType::Strong),
4050                            availability: Some(fdecl::Availability::Optional),
4051                            ..Default::default()
4052                        }
4053                    ),
4054                ]),
4055                children: Some(vec![
4056                    fdecl::Child {
4057                        name: Some("logger".to_string()),
4058                        url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
4059                        startup: Some(fdecl::StartupMode::Lazy),
4060                        ..Default::default()
4061                    },
4062                    fdecl::Child {
4063                        name: Some("target".to_string()),
4064                        url: Some("#meta/target.cm".to_string()),
4065                        startup: Some(fdecl::StartupMode::Lazy),
4066                        ..Default::default()
4067                    },
4068                ]),
4069                ..default_component_decl()
4070            },
4071        },
4072
4073        test_compile_offer => {
4074            input = json!({
4075                "offer": [
4076                    {
4077                        "protocol": "fuchsia.logger.LegacyLog",
4078                        "from": "#logger",
4079                        "to": "#netstack", // Verifies compilation of singleton "to:".
4080                        "dependency": "weak"
4081                    },
4082                    {
4083                        "protocol": "fuchsia.logger.LegacyLog",
4084                        "from": "#logger",
4085                        "to": [ "#modular" ], // Verifies compilation of "to:" as array of one element.
4086                        "as": "fuchsia.logger.LegacySysLog",
4087                        "dependency": "strong"
4088                    },
4089                    {
4090                        "protocol": [
4091                            "fuchsia.setui.SetUiService",
4092                            "fuchsia.test.service.Name"
4093                        ],
4094                        "from": "parent",
4095                        "to": [ "#modular" ],
4096                        "availability": "optional"
4097                    },
4098                    {
4099                        "protocol": "fuchsia.sys2.StorageAdmin",
4100                        "from": "#data",
4101                        "to": [ "#modular" ],
4102                    },
4103                    {
4104                        "protocol": "fuchsia.sys2.FromDict",
4105                        "from": "parent/in/dict",
4106                        "to": [ "#modular" ],
4107                    },
4108                    {
4109                        "service": "svc",
4110                        "from": [ "parent", "self", "#logger", "#modular" ],
4111                        "to": "#netstack",
4112                    },
4113                    {
4114                        "service": "fuchsia.sys2.FromDictService",
4115                        "from": [ "parent/in/dict"],
4116                        "to": "#modular",
4117                        "dependency": "weak",
4118                    },
4119                    {
4120                        "directory": "assets",
4121                        "from": "parent",
4122                        "to": [ "#netstack" ],
4123                        "dependency": "weak",
4124                        "availability": "same_as_target"
4125                    },
4126                    {
4127                        "directory": [ "assets2", "assets3" ],
4128                        "from": "parent",
4129                        "to": [ "#modular", "#netstack" ],
4130                    },
4131                    {
4132                        "directory": "data",
4133                        "from": "parent",
4134                        "to": [ "#modular" ],
4135                        "as": "assets",
4136                        "subdir": "index/file",
4137                        "dependency": "strong"
4138                    },
4139                    {
4140                        "directory": "hub",
4141                        "from": "framework",
4142                        "to": [ "#modular" ],
4143                        "as": "hub",
4144                    },
4145                    {
4146                        "storage": "data",
4147                        "from": "self",
4148                        "to": [
4149                            "#netstack",
4150                            "#modular"
4151                        ],
4152                    },
4153                    {
4154                        "storage": [ "storage_a", "storage_b" ],
4155                        "from": "parent",
4156                        "to": "#netstack",
4157                    },
4158                    {
4159                        "runner": "elf",
4160                        "from": "parent",
4161                        "to": [ "#modular" ],
4162                        "as": "elf-renamed",
4163                    },
4164                    {
4165                        "runner": [ "runner_a", "runner_b" ],
4166                        "from": "parent",
4167                        "to": "#netstack",
4168                    },
4169                    {
4170                        "resolver": "my_resolver",
4171                        "from": "parent",
4172                        "to": [ "#modular" ],
4173                        "as": "pkg_resolver",
4174                    },
4175                    {
4176                        "resolver": [ "resolver_a", "resolver_b" ],
4177                        "from": "parent",
4178                        "to": "#netstack",
4179                    },
4180                    {
4181                        "dictionary": [ "dictionary_a", "dictionary_b" ],
4182                        "from": "parent",
4183                        "to": "#netstack",
4184                    },
4185                    {
4186                        "event_stream": [
4187                            "running",
4188                            "started",
4189                        ],
4190                        "from": "parent",
4191                        "to": "#netstack",
4192                    },
4193                    {
4194                        "event_stream": "stopped",
4195                        "from": "parent",
4196                        "to": "#netstack",
4197                        "as": "some_other_event",
4198                    },
4199                ],
4200                "children": [
4201                    {
4202                        "name": "logger",
4203                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
4204                    },
4205                    {
4206                        "name": "netstack",
4207                        "url": "fuchsia-pkg://fuchsia.com/netstack/stable#meta/netstack.cm"
4208                    },
4209                ],
4210                "collections": [
4211                    {
4212                        "name": "modular",
4213                        "durability": "transient",
4214                    },
4215                ],
4216                "capabilities": [
4217                    {
4218                        "service": "svc",
4219                    },
4220                    {
4221                        "storage": "data",
4222                        "backing_dir": "minfs",
4223                        "from": "#logger",
4224                        "storage_id": "static_instance_id_or_moniker",
4225                    },
4226                ],
4227            }),
4228            output = fdecl::Component {
4229                offers: Some(vec![
4230                    fdecl::Offer::Protocol (
4231                        fdecl::OfferProtocol {
4232                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
4233                                name: "logger".to_string(),
4234                                collection: None,
4235                            })),
4236                            source_name: Some("fuchsia.logger.LegacyLog".to_string()),
4237                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4238                                name: "netstack".to_string(),
4239                                collection: None,
4240                            })),
4241                            target_name: Some("fuchsia.logger.LegacyLog".to_string()),
4242                            dependency_type: Some(fdecl::DependencyType::Weak),
4243                            availability: Some(fdecl::Availability::Required),
4244                            ..Default::default()
4245                        }
4246                    ),
4247                    fdecl::Offer::Protocol (
4248                        fdecl::OfferProtocol {
4249                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
4250                                name: "logger".to_string(),
4251                                collection: None,
4252                            })),
4253                            source_name: Some("fuchsia.logger.LegacyLog".to_string()),
4254                            target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
4255                                name: "modular".to_string(),
4256                            })),
4257                            target_name: Some("fuchsia.logger.LegacySysLog".to_string()),
4258                            dependency_type: Some(fdecl::DependencyType::Strong),
4259                            availability: Some(fdecl::Availability::Required),
4260                            ..Default::default()
4261                        }
4262                    ),
4263                    fdecl::Offer::Protocol (
4264                        fdecl::OfferProtocol {
4265                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4266                            source_name: Some("fuchsia.setui.SetUiService".to_string()),
4267                            target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
4268                                name: "modular".to_string(),
4269                            })),
4270                            target_name: Some("fuchsia.setui.SetUiService".to_string()),
4271                            dependency_type: Some(fdecl::DependencyType::Strong),
4272                            availability: Some(fdecl::Availability::Optional),
4273                            ..Default::default()
4274                        }
4275                    ),
4276                    fdecl::Offer::Protocol (
4277                        fdecl::OfferProtocol {
4278                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4279                            source_name: Some("fuchsia.test.service.Name".to_string()),
4280                            target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
4281                                name: "modular".to_string(),
4282                            })),
4283                            target_name: Some("fuchsia.test.service.Name".to_string()),
4284                            dependency_type: Some(fdecl::DependencyType::Strong),
4285                            availability: Some(fdecl::Availability::Optional),
4286                            ..Default::default()
4287                        }
4288                    ),
4289                    fdecl::Offer::Protocol (
4290                        fdecl::OfferProtocol {
4291                            source: Some(fdecl::Ref::Capability(fdecl::CapabilityRef {
4292                                name: "data".to_string(),
4293                            })),
4294                            source_name: Some("fuchsia.sys2.StorageAdmin".to_string()),
4295                            target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
4296                                name: "modular".to_string(),
4297                            })),
4298                            target_name: Some("fuchsia.sys2.StorageAdmin".to_string()),
4299                            dependency_type: Some(fdecl::DependencyType::Strong),
4300                            availability: Some(fdecl::Availability::Required),
4301                            ..Default::default()
4302                        }
4303                    ),
4304                    fdecl::Offer::Protocol (
4305                        fdecl::OfferProtocol {
4306                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4307                            source_dictionary: Some("in/dict".into()),
4308                            source_name: Some("fuchsia.sys2.FromDict".to_string()),
4309                            target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
4310                                name: "modular".to_string(),
4311                            })),
4312                            target_name: Some("fuchsia.sys2.FromDict".to_string()),
4313                            dependency_type: Some(fdecl::DependencyType::Strong),
4314                            availability: Some(fdecl::Availability::Required),
4315                            ..Default::default()
4316                        }
4317                    ),
4318                    fdecl::Offer::Service (
4319                        fdecl::OfferService {
4320                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4321                            source_name: Some("svc".into()),
4322                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4323                                name: "netstack".into(),
4324                                collection: None,
4325                            })),
4326                            target_name: Some("svc".into()),
4327                            availability: Some(fdecl::Availability::Required),
4328                            dependency_type: Some(fdecl::DependencyType::Strong),
4329                            ..Default::default()
4330                        }
4331                    ),
4332                    fdecl::Offer::Service (
4333                        fdecl::OfferService {
4334                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
4335                            source_name: Some("svc".into()),
4336                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4337                                name: "netstack".into(),
4338                                collection: None,
4339                            })),
4340                            target_name: Some("svc".into()),
4341                            availability: Some(fdecl::Availability::Required),
4342                            dependency_type: Some(fdecl::DependencyType::Strong),
4343                            ..Default::default()
4344                        }
4345                    ),
4346                    fdecl::Offer::Service (
4347                        fdecl::OfferService {
4348                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
4349                                name: "logger".into(),
4350                                collection: None,
4351                            })),
4352                            source_name: Some("svc".into()),
4353                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4354                                name: "netstack".into(),
4355                                collection: None,
4356                            })),
4357                            target_name: Some("svc".into()),
4358                            availability: Some(fdecl::Availability::Required),
4359                            dependency_type: Some(fdecl::DependencyType::Strong),
4360                            ..Default::default()
4361                        }
4362                    ),
4363                    fdecl::Offer::Service (
4364                        fdecl::OfferService {
4365                            source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
4366                                name: "modular".into(),
4367                            })),
4368                            source_name: Some("svc".into()),
4369                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4370                                name: "netstack".into(),
4371                                collection: None,
4372                            })),
4373                            target_name: Some("svc".into()),
4374                            availability: Some(fdecl::Availability::Required),
4375                            dependency_type: Some(fdecl::DependencyType::Strong),
4376                            ..Default::default()
4377                        }
4378                    ),
4379                    fdecl::Offer::Service (
4380                        fdecl::OfferService {
4381                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4382                            source_name: Some("fuchsia.sys2.FromDictService".into()),
4383                            source_dictionary: Some("in/dict".into()),
4384                            target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
4385                                name: "modular".into(),
4386                            })),
4387                            target_name: Some("fuchsia.sys2.FromDictService".to_string()),
4388                            availability: Some(fdecl::Availability::Required),
4389                            dependency_type: Some(fdecl::DependencyType::Weak),
4390                            ..Default::default()
4391                        }
4392                    ),
4393                    fdecl::Offer::Directory (
4394                        fdecl::OfferDirectory {
4395                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4396                            source_name: Some("assets".to_string()),
4397                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4398                                name: "netstack".to_string(),
4399                                collection: None,
4400                            })),
4401                            target_name: Some("assets".to_string()),
4402                            rights: None,
4403                            subdir: None,
4404                            dependency_type: Some(fdecl::DependencyType::Weak),
4405                            availability: Some(fdecl::Availability::SameAsTarget),
4406                            ..Default::default()
4407                        }
4408                    ),
4409                    fdecl::Offer::Directory (
4410                        fdecl::OfferDirectory {
4411                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4412                            source_name: Some("assets2".to_string()),
4413                            target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
4414                                name: "modular".to_string(),
4415                            })),
4416                            target_name: Some("assets2".to_string()),
4417                            rights: None,
4418                            subdir: None,
4419                            dependency_type: Some(fdecl::DependencyType::Strong),
4420                            availability: Some(fdecl::Availability::Required),
4421                            ..Default::default()
4422                        }
4423                    ),
4424                    fdecl::Offer::Directory (
4425                        fdecl::OfferDirectory {
4426                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4427                            source_name: Some("assets3".to_string()),
4428                            target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
4429                                name: "modular".to_string(),
4430                            })),
4431                            target_name: Some("assets3".to_string()),
4432                            rights: None,
4433                            subdir: None,
4434                            dependency_type: Some(fdecl::DependencyType::Strong),
4435                            availability: Some(fdecl::Availability::Required),
4436                            ..Default::default()
4437                        }
4438                    ),
4439                    fdecl::Offer::Directory (
4440                        fdecl::OfferDirectory {
4441                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4442                            source_name: Some("assets2".to_string()),
4443                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4444                                name: "netstack".to_string(),
4445                                collection: None,
4446                            })),
4447                            target_name: Some("assets2".to_string()),
4448                            rights: None,
4449                            subdir: None,
4450                            dependency_type: Some(fdecl::DependencyType::Strong),
4451                            availability: Some(fdecl::Availability::Required),
4452                            ..Default::default()
4453                        }
4454                    ),
4455                    fdecl::Offer::Directory (
4456                        fdecl::OfferDirectory {
4457                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4458                            source_name: Some("assets3".to_string()),
4459                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4460                                name: "netstack".to_string(),
4461                                collection: None,
4462                            })),
4463                            target_name: Some("assets3".to_string()),
4464                            rights: None,
4465                            subdir: None,
4466                            dependency_type: Some(fdecl::DependencyType::Strong),
4467                            availability: Some(fdecl::Availability::Required),
4468                            ..Default::default()
4469                        }
4470                    ),
4471                    fdecl::Offer::Directory (
4472                        fdecl::OfferDirectory {
4473                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4474                            source_name: Some("data".to_string()),
4475                            target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
4476                                name: "modular".to_string(),
4477                            })),
4478                            target_name: Some("assets".to_string()),
4479                            rights: None,
4480                            subdir: Some("index/file".to_string()),
4481                            dependency_type: Some(fdecl::DependencyType::Strong),
4482                            availability: Some(fdecl::Availability::Required),
4483                            ..Default::default()
4484                        }
4485                    ),
4486                    fdecl::Offer::Directory (
4487                        fdecl::OfferDirectory {
4488                            source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef {})),
4489                            source_name: Some("hub".to_string()),
4490                            target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
4491                                name: "modular".to_string(),
4492                            })),
4493                            target_name: Some("hub".to_string()),
4494                            rights: None,
4495                            subdir: None,
4496                            dependency_type: Some(fdecl::DependencyType::Strong),
4497                            availability: Some(fdecl::Availability::Required),
4498                            ..Default::default()
4499                        }
4500                    ),
4501                    fdecl::Offer::Storage (
4502                        fdecl::OfferStorage {
4503                            source_name: Some("data".to_string()),
4504                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
4505                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4506                                name: "netstack".to_string(),
4507                                collection: None,
4508                            })),
4509                            target_name: Some("data".to_string()),
4510                            availability: Some(fdecl::Availability::Required),
4511                            ..Default::default()
4512                        }
4513                    ),
4514                    fdecl::Offer::Storage (
4515                        fdecl::OfferStorage {
4516                            source_name: Some("data".to_string()),
4517                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
4518                            target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
4519                                name: "modular".to_string(),
4520                            })),
4521                            target_name: Some("data".to_string()),
4522                            availability: Some(fdecl::Availability::Required),
4523                            ..Default::default()
4524                        }
4525                    ),
4526                    fdecl::Offer::Storage (
4527                        fdecl::OfferStorage {
4528                            source_name: Some("storage_a".to_string()),
4529                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4530                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4531                                name: "netstack".to_string(),
4532                                collection: None,
4533                            })),
4534                            target_name: Some("storage_a".to_string()),
4535                            availability: Some(fdecl::Availability::Required),
4536                            ..Default::default()
4537                        }
4538                    ),
4539                    fdecl::Offer::Storage (
4540                        fdecl::OfferStorage {
4541                            source_name: Some("storage_b".to_string()),
4542                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4543                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4544                                name: "netstack".to_string(),
4545                                collection: None,
4546                            })),
4547                            target_name: Some("storage_b".to_string()),
4548                            availability: Some(fdecl::Availability::Required),
4549                            ..Default::default()
4550                        }
4551                    ),
4552                    fdecl::Offer::Runner (
4553                        fdecl::OfferRunner {
4554                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4555                            source_name: Some("elf".to_string()),
4556                            target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
4557                                name: "modular".to_string(),
4558                            })),
4559                            target_name: Some("elf-renamed".to_string()),
4560                            ..Default::default()
4561                        }
4562                    ),
4563                    fdecl::Offer::Runner (
4564                        fdecl::OfferRunner {
4565                            source_name: Some("runner_a".to_string()),
4566                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4567                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4568                                name: "netstack".to_string(),
4569                                collection: None,
4570                            })),
4571                            target_name: Some("runner_a".to_string()),
4572                            ..Default::default()
4573                        }
4574                    ),
4575                    fdecl::Offer::Runner (
4576                        fdecl::OfferRunner {
4577                            source_name: Some("runner_b".to_string()),
4578                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4579                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4580                                name: "netstack".to_string(),
4581                                collection: None,
4582                            })),
4583                            target_name: Some("runner_b".to_string()),
4584                            ..Default::default()
4585                        }
4586                    ),
4587                    fdecl::Offer::Resolver (
4588                        fdecl::OfferResolver {
4589                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4590                            source_name: Some("my_resolver".to_string()),
4591                            target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
4592                                name: "modular".to_string(),
4593                            })),
4594                            target_name: Some("pkg_resolver".to_string()),
4595                            ..Default::default()
4596                        }
4597                    ),
4598                    fdecl::Offer::Resolver (
4599                        fdecl::OfferResolver {
4600                            source_name: Some("resolver_a".to_string()),
4601                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4602                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4603                                name: "netstack".to_string(),
4604                                collection: None,
4605                            })),
4606                            target_name: Some("resolver_a".to_string()),
4607                            ..Default::default()
4608                        }
4609                    ),
4610                    fdecl::Offer::Resolver (
4611                        fdecl::OfferResolver {
4612                            source_name: Some("resolver_b".to_string()),
4613                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4614                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4615                                name: "netstack".to_string(),
4616                                collection: None,
4617                            })),
4618                            target_name: Some("resolver_b".to_string()),
4619                            ..Default::default()
4620                        }
4621                    ),
4622                    fdecl::Offer::Dictionary (
4623                        fdecl::OfferDictionary {
4624                            source_name: Some("dictionary_a".to_string()),
4625                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4626                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4627                                name: "netstack".to_string(),
4628                                collection: None,
4629                            })),
4630                            target_name: Some("dictionary_a".to_string()),
4631                            dependency_type: Some(fdecl::DependencyType::Strong),
4632                            availability: Some(fdecl::Availability::Required),
4633                            ..Default::default()
4634                        }
4635                    ),
4636                    fdecl::Offer::Dictionary (
4637                        fdecl::OfferDictionary {
4638                            source_name: Some("dictionary_b".to_string()),
4639                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4640                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4641                                name: "netstack".to_string(),
4642                                collection: None,
4643                            })),
4644                            target_name: Some("dictionary_b".to_string()),
4645                            dependency_type: Some(fdecl::DependencyType::Strong),
4646                            availability: Some(fdecl::Availability::Required),
4647                            ..Default::default()
4648                        }
4649                    ),
4650                    fdecl::Offer::EventStream (
4651                        fdecl::OfferEventStream {
4652                            source_name: Some("running".to_string()),
4653                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4654                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4655                                name: "netstack".to_string(),
4656                                collection: None,
4657                            })),
4658                            target_name: Some("running".to_string()),
4659                            availability: Some(fdecl::Availability::Required),
4660                            ..Default::default()
4661                        }
4662                    ),
4663                    fdecl::Offer::EventStream (
4664                        fdecl::OfferEventStream {
4665                            source_name: Some("started".to_string()),
4666                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4667                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4668                                name: "netstack".to_string(),
4669                                collection: None,
4670                            })),
4671                            target_name: Some("started".to_string()),
4672                            availability: Some(fdecl::Availability::Required),
4673                            ..Default::default()
4674                        }
4675                    ),
4676                    fdecl::Offer::EventStream (
4677                        fdecl::OfferEventStream {
4678                            source_name: Some("stopped".to_string()),
4679                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4680                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4681                                name: "netstack".to_string(),
4682                                collection: None,
4683                            })),
4684                            target_name: Some("some_other_event".to_string()),
4685                            availability: Some(fdecl::Availability::Required),
4686                            ..Default::default()
4687                        }
4688                    ),
4689                ]),
4690                capabilities: Some(vec![
4691                    fdecl::Capability::Service (
4692                        fdecl::Service {
4693                            name: Some("svc".into()),
4694                            source_path: Some("/svc/svc".into()),
4695                            ..Default::default()
4696                        },
4697                    ),
4698                    fdecl::Capability::Storage (
4699                        fdecl::Storage {
4700                            name: Some("data".to_string()),
4701                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
4702                                name: "logger".to_string(),
4703                                collection: None,
4704                            })),
4705                            backing_dir: Some("minfs".to_string()),
4706                            subdir: None,
4707                            storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker),
4708                            ..Default::default()
4709                        }
4710                    )
4711                ]),
4712                children: Some(vec![
4713                    fdecl::Child {
4714                        name: Some("logger".to_string()),
4715                        url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
4716                        startup: Some(fdecl::StartupMode::Lazy),
4717                        environment: None,
4718                        on_terminate: None,
4719                        ..Default::default()
4720                    },
4721                    fdecl::Child {
4722                        name: Some("netstack".to_string()),
4723                        url: Some("fuchsia-pkg://fuchsia.com/netstack/stable#meta/netstack.cm".to_string()),
4724                        startup: Some(fdecl::StartupMode::Lazy),
4725                        environment: None,
4726                        on_terminate: None,
4727                        ..Default::default()
4728                    },
4729                ]),
4730                collections: Some(vec![
4731                    fdecl::Collection {
4732                        name: Some("modular".to_string()),
4733                        durability: Some(fdecl::Durability::Transient),
4734                        environment: None,
4735                        allowed_offers: None,
4736                        ..Default::default()
4737                    }
4738                ]),
4739                ..default_component_decl()
4740            },
4741        },
4742
4743        test_compile_offer_route_to_dictionary => {
4744            input = json!({
4745                "offer": [
4746                    {
4747                        "protocol": "A",
4748                        "from": "parent/dict/1",
4749                        "to": "self/dict",
4750                    },
4751                    {
4752                        "runner": "B",
4753                        "from": "#child",
4754                        "to": "self/dict",
4755                    },
4756                    {
4757                        "config": "B",
4758                        "from": "parent/dict/2",
4759                        "to": "self/dict",
4760                        "as": "C",
4761                    },
4762                ],
4763                "children": [
4764                    {
4765                        "name": "child",
4766                        "url": "fuchsia-pkg://child"
4767                    },
4768                ],
4769                "capabilities": [
4770                    {
4771                        "dictionary": "dict",
4772                    },
4773                ],
4774            }),
4775            output = fdecl::Component {
4776                offers: Some(vec![
4777                    fdecl::Offer::Protocol (
4778                        fdecl::OfferProtocol {
4779                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4780                            source_dictionary: Some("dict/1".into()),
4781                            source_name: Some("A".into()),
4782                            target: Some(fdecl::Ref::Capability(fdecl::CapabilityRef {
4783                                name: "dict".to_string(),
4784                            })),
4785                            target_name: Some("A".into()),
4786                            dependency_type: Some(fdecl::DependencyType::Strong),
4787                            availability: Some(fdecl::Availability::Required),
4788                            ..Default::default()
4789                        }
4790                    ),
4791                    fdecl::Offer::Runner (
4792                        fdecl::OfferRunner {
4793                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
4794                                name: "child".into(),
4795                                collection: None,
4796                            })),
4797                            source_name: Some("B".into()),
4798                            target: Some(fdecl::Ref::Capability(fdecl::CapabilityRef {
4799                                name: "dict".to_string(),
4800                            })),
4801                            target_name: Some("B".into()),
4802                            ..Default::default()
4803                        }
4804                    ),
4805                    fdecl::Offer::Config (
4806                        fdecl::OfferConfiguration {
4807                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4808                            source_dictionary: Some("dict/2".into()),
4809                            source_name: Some("B".into()),
4810                            target: Some(fdecl::Ref::Capability(fdecl::CapabilityRef {
4811                                name: "dict".to_string(),
4812                            })),
4813                            target_name: Some("C".into()),
4814                            availability: Some(fdecl::Availability::Required),
4815                            ..Default::default()
4816                        }
4817                    ),
4818                ]),
4819                capabilities: Some(vec![
4820                    fdecl::Capability::Dictionary (
4821                        fdecl::Dictionary {
4822                            name: Some("dict".into()),
4823                            ..Default::default()
4824                        }
4825                    )
4826                ]),
4827                children: Some(vec![
4828                    fdecl::Child {
4829                        name: Some("child".to_string()),
4830                        url: Some("fuchsia-pkg://child".to_string()),
4831                        startup: Some(fdecl::StartupMode::Lazy),
4832                        ..Default::default()
4833                    },
4834                ]),
4835                ..default_component_decl()
4836            },
4837        },
4838
4839
4840        test_compile_children => {
4841            input = json!({
4842                "children": [
4843                    {
4844                        "name": "logger",
4845                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
4846                    },
4847                    {
4848                        "name": "gmail",
4849                        "url": "https://www.google.com/gmail",
4850                        "startup": "eager",
4851                    },
4852                    {
4853                        "name": "echo",
4854                        "url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo.cm",
4855                        "startup": "lazy",
4856                        "on_terminate": "reboot",
4857                        "environment": "#myenv",
4858                    },
4859                ],
4860                "environments": [
4861                    {
4862                        "name": "myenv",
4863                        "extends": "realm",
4864                    },
4865                ],
4866            }),
4867            output = fdecl::Component {
4868                children: Some(vec![
4869                    fdecl::Child {
4870                        name: Some("logger".to_string()),
4871                        url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
4872                        startup: Some(fdecl::StartupMode::Lazy),
4873                        environment: None,
4874                        on_terminate: None,
4875                        ..Default::default()
4876                    },
4877                    fdecl::Child {
4878                        name: Some("gmail".to_string()),
4879                        url: Some("https://www.google.com/gmail".to_string()),
4880                        startup: Some(fdecl::StartupMode::Eager),
4881                        environment: None,
4882                        on_terminate: None,
4883                        ..Default::default()
4884                    },
4885                    fdecl::Child {
4886                        name: Some("echo".to_string()),
4887                        url: Some("fuchsia-pkg://fuchsia.com/echo/stable#meta/echo.cm".to_string()),
4888                        startup: Some(fdecl::StartupMode::Lazy),
4889                        environment: Some("myenv".to_string()),
4890                        on_terminate: Some(fdecl::OnTerminate::Reboot),
4891                        ..Default::default()
4892                    }
4893                ]),
4894                environments: Some(vec![
4895                    fdecl::Environment {
4896                        name: Some("myenv".to_string()),
4897                        extends: Some(fdecl::EnvironmentExtends::Realm),
4898                        runners: None,
4899                        resolvers: None,
4900                        stop_timeout_ms: None,
4901                        ..Default::default()
4902                    }
4903                ]),
4904                ..default_component_decl()
4905            },
4906        },
4907
4908        test_compile_collections => {
4909            input = json!({
4910                "collections": [
4911                    {
4912                        "name": "modular",
4913                        "durability": "single_run",
4914                    },
4915                    {
4916                        "name": "tests",
4917                        "durability": "transient",
4918                        "environment": "#myenv",
4919                    },
4920                ],
4921                "environments": [
4922                    {
4923                        "name": "myenv",
4924                        "extends": "realm",
4925                    }
4926                ],
4927            }),
4928            output = fdecl::Component {
4929                collections: Some(vec![
4930                    fdecl::Collection {
4931                        name: Some("modular".to_string()),
4932                        durability: Some(fdecl::Durability::SingleRun),
4933                        environment: None,
4934                        allowed_offers: None,
4935                        ..Default::default()
4936                    },
4937                    fdecl::Collection {
4938                        name: Some("tests".to_string()),
4939                        durability: Some(fdecl::Durability::Transient),
4940                        environment: Some("myenv".to_string()),
4941                        allowed_offers: None,
4942                        ..Default::default()
4943                    }
4944                ]),
4945                environments: Some(vec![
4946                    fdecl::Environment {
4947                        name: Some("myenv".to_string()),
4948                        extends: Some(fdecl::EnvironmentExtends::Realm),
4949                        runners: None,
4950                        resolvers: None,
4951                        stop_timeout_ms: None,
4952                        ..Default::default()
4953                    }
4954                ]),
4955                ..default_component_decl()
4956            },
4957        },
4958
4959        test_compile_capabilities => {
4960            features = FeatureSet::from(vec![Feature::DynamicDictionaries]),
4961            input = json!({
4962                "capabilities": [
4963                    {
4964                        "protocol": "myprotocol",
4965                        "path": "/protocol",
4966                    },
4967                    {
4968                        "protocol": "myprotocol2",
4969                    },
4970                    {
4971                        "protocol": [ "myprotocol3", "myprotocol4" ],
4972                    },
4973                    {
4974                        "directory": "mydirectory",
4975                        "path": "/directory",
4976                        "rights": [ "connect" ],
4977                    },
4978                    {
4979                        "storage": "mystorage",
4980                        "backing_dir": "storage",
4981                        "from": "#minfs",
4982                        "storage_id": "static_instance_id_or_moniker",
4983                    },
4984                    {
4985                        "storage": "mystorage2",
4986                        "backing_dir": "storage2",
4987                        "from": "#minfs",
4988                        "storage_id": "static_instance_id",
4989                    },
4990                    {
4991                        "runner": "myrunner",
4992                        "path": "/runner",
4993                    },
4994                    {
4995                        "resolver": "myresolver",
4996                        "path": "/resolver"
4997                    },
4998                    {
4999                        "dictionary": "dict1",
5000                    },
5001                    {
5002                        "dictionary": "dict2",
5003                        "path": "/in/a",
5004                    },
5005                ],
5006                "children": [
5007                    {
5008                        "name": "minfs",
5009                        "url": "fuchsia-pkg://fuchsia.com/minfs/stable#meta/minfs.cm",
5010                    },
5011                ]
5012            }),
5013            output = fdecl::Component {
5014                capabilities: Some(vec![
5015                    fdecl::Capability::Protocol (
5016                        fdecl::Protocol {
5017                            name: Some("myprotocol".to_string()),
5018                            source_path: Some("/protocol".to_string()),
5019                            ..Default::default()
5020                        }
5021                    ),
5022                    fdecl::Capability::Protocol (
5023                        fdecl::Protocol {
5024                            name: Some("myprotocol2".to_string()),
5025                            source_path: Some("/svc/myprotocol2".to_string()),
5026                            ..Default::default()
5027                        }
5028                    ),
5029                    fdecl::Capability::Protocol (
5030                        fdecl::Protocol {
5031                            name: Some("myprotocol3".to_string()),
5032                            source_path: Some("/svc/myprotocol3".to_string()),
5033                            ..Default::default()
5034                        }
5035                    ),
5036                    fdecl::Capability::Protocol (
5037                        fdecl::Protocol {
5038                            name: Some("myprotocol4".to_string()),
5039                            source_path: Some("/svc/myprotocol4".to_string()),
5040                            ..Default::default()
5041                        }
5042                    ),
5043                    fdecl::Capability::Directory (
5044                        fdecl::Directory {
5045                            name: Some("mydirectory".to_string()),
5046                            source_path: Some("/directory".to_string()),
5047                            rights: Some(fio::Operations::CONNECT),
5048                            ..Default::default()
5049                        }
5050                    ),
5051                    fdecl::Capability::Storage (
5052                        fdecl::Storage {
5053                            name: Some("mystorage".to_string()),
5054                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5055                                name: "minfs".to_string(),
5056                                collection: None,
5057                            })),
5058                            backing_dir: Some("storage".to_string()),
5059                            subdir: None,
5060                            storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker),
5061                            ..Default::default()
5062                        }
5063                    ),
5064                    fdecl::Capability::Storage (
5065                        fdecl::Storage {
5066                            name: Some("mystorage2".to_string()),
5067                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5068                                name: "minfs".to_string(),
5069                                collection: None,
5070                            })),
5071                            backing_dir: Some("storage2".to_string()),
5072                            subdir: None,
5073                            storage_id: Some(fdecl::StorageId::StaticInstanceId),
5074                            ..Default::default()
5075                        }
5076                    ),
5077                    fdecl::Capability::Runner (
5078                        fdecl::Runner {
5079                            name: Some("myrunner".to_string()),
5080                            source_path: Some("/runner".to_string()),
5081                            ..Default::default()
5082                        }
5083                    ),
5084                    fdecl::Capability::Resolver (
5085                        fdecl::Resolver {
5086                            name: Some("myresolver".to_string()),
5087                            source_path: Some("/resolver".to_string()),
5088                            ..Default::default()
5089                        }
5090                    ),
5091                    fdecl::Capability::Dictionary (
5092                        fdecl::Dictionary {
5093                            name: Some("dict1".into()),
5094                            ..Default::default()
5095                        }
5096                    ),
5097                    fdecl::Capability::Dictionary (
5098                        fdecl::Dictionary {
5099                            name: Some("dict2".into()),
5100                            source: None,
5101                            source_dictionary: None,
5102                            source_path: Some("/in/a".into()),
5103                            ..Default::default()
5104                        }
5105                    ),
5106                ]),
5107                children: Some(vec![
5108                    fdecl::Child {
5109                        name: Some("minfs".to_string()),
5110                        url: Some("fuchsia-pkg://fuchsia.com/minfs/stable#meta/minfs.cm".to_string()),
5111                        startup: Some(fdecl::StartupMode::Lazy),
5112                        environment: None,
5113                        on_terminate: None,
5114                        ..Default::default()
5115                    }
5116                ]),
5117                ..default_component_decl()
5118            },
5119        },
5120
5121        test_compile_facets => {
5122            input = json!({
5123                "facets": {
5124                    "title": "foo",
5125                    "authors": [ "me", "you" ],
5126                    "year": "2018",
5127                    "metadata": {
5128                        "publisher": "The Books Publisher",
5129                    }
5130                }
5131            }),
5132            output = fdecl::Component {
5133                facets: Some(fdata::Dictionary {
5134                        entries: Some(vec![
5135                            fdata::DictionaryEntry {
5136                                key: "authors".to_string(),
5137                                value: Some(Box::new(fdata::DictionaryValue::StrVec(vec!["me".to_owned(), "you".to_owned()]))),
5138                            },
5139                            fdata::DictionaryEntry {
5140                                key: "metadata.publisher".to_string(),
5141                                value: Some(Box::new(fdata::DictionaryValue::Str("The Books Publisher".to_string()))),
5142                            },
5143                            fdata::DictionaryEntry {
5144                                key: "title".to_string(),
5145                                value: Some(Box::new(fdata::DictionaryValue::Str("foo".to_string()))),
5146                            },
5147                            fdata::DictionaryEntry {
5148                                key: "year".to_string(),
5149                                value: Some(Box::new(fdata::DictionaryValue::Str("2018".to_string()))),
5150                            },
5151                        ]),
5152                        ..Default::default()
5153                    }
5154            ),
5155            ..default_component_decl()
5156            },
5157        },
5158
5159        test_compile_environment => {
5160            input = json!({
5161                "environments": [
5162                    {
5163                        "name": "myenv",
5164                        "__stop_timeout_ms": 10u32,
5165                    },
5166                    {
5167                        "name": "myenv2",
5168                        "extends": "realm",
5169                    },
5170                    {
5171                        "name": "myenv3",
5172                        "extends": "none",
5173                        "__stop_timeout_ms": 8000u32,
5174                    }
5175                ],
5176            }),
5177            output = fdecl::Component {
5178                environments: Some(vec![
5179                    fdecl::Environment {
5180                        name: Some("myenv".to_string()),
5181                        extends: Some(fdecl::EnvironmentExtends::None),
5182                        runners: None,
5183                        resolvers: None,
5184                        stop_timeout_ms: Some(10),
5185                        ..Default::default()
5186                    },
5187                    fdecl::Environment {
5188                        name: Some("myenv2".to_string()),
5189                        extends: Some(fdecl::EnvironmentExtends::Realm),
5190                        runners: None,
5191                        resolvers: None,
5192                        stop_timeout_ms: None,
5193                        ..Default::default()
5194                    },
5195                    fdecl::Environment {
5196                        name: Some("myenv3".to_string()),
5197                        extends: Some(fdecl::EnvironmentExtends::None),
5198                        runners: None,
5199                        resolvers: None,
5200                        stop_timeout_ms: Some(8000),
5201                        ..Default::default()
5202                    },
5203                ]),
5204                ..default_component_decl()
5205            },
5206        },
5207
5208        test_compile_environment_with_runner_and_resolver => {
5209            input = json!({
5210                "environments": [
5211                    {
5212                        "name": "myenv",
5213                        "extends": "realm",
5214                        "runners": [
5215                            {
5216                                "runner": "dart",
5217                                "from": "parent",
5218                            }
5219                        ],
5220                        "resolvers": [
5221                            {
5222                                "resolver": "pkg_resolver",
5223                                "from": "parent",
5224                                "scheme": "fuchsia-pkg",
5225                            }
5226                        ],
5227                    },
5228                ],
5229            }),
5230            output = fdecl::Component {
5231                environments: Some(vec![
5232                    fdecl::Environment {
5233                        name: Some("myenv".to_string()),
5234                        extends: Some(fdecl::EnvironmentExtends::Realm),
5235                        runners: Some(vec![
5236                            fdecl::RunnerRegistration {
5237                                source_name: Some("dart".to_string()),
5238                                source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5239                                target_name: Some("dart".to_string()),
5240                                ..Default::default()
5241                            }
5242                        ]),
5243                        resolvers: Some(vec![
5244                            fdecl::ResolverRegistration {
5245                                resolver: Some("pkg_resolver".to_string()),
5246                                source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5247                                scheme: Some("fuchsia-pkg".to_string()),
5248                                ..Default::default()
5249                            }
5250                        ]),
5251                        stop_timeout_ms: None,
5252                        ..Default::default()
5253                    },
5254                ]),
5255                ..default_component_decl()
5256            },
5257        },
5258
5259        test_compile_environment_with_runner_alias => {
5260            input = json!({
5261                "environments": [
5262                    {
5263                        "name": "myenv",
5264                        "extends": "realm",
5265                        "runners": [
5266                            {
5267                                "runner": "dart",
5268                                "from": "parent",
5269                                "as": "my-dart",
5270                            }
5271                        ],
5272                    },
5273                ],
5274            }),
5275            output = fdecl::Component {
5276                environments: Some(vec![
5277                    fdecl::Environment {
5278                        name: Some("myenv".to_string()),
5279                        extends: Some(fdecl::EnvironmentExtends::Realm),
5280                        runners: Some(vec![
5281                            fdecl::RunnerRegistration {
5282                                source_name: Some("dart".to_string()),
5283                                source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5284                                target_name: Some("my-dart".to_string()),
5285                                ..Default::default()
5286                            }
5287                        ]),
5288                        resolvers: None,
5289                        stop_timeout_ms: None,
5290                        ..Default::default()
5291                    },
5292                ]),
5293                ..default_component_decl()
5294            },
5295        },
5296
5297        test_compile_environment_with_debug => {
5298            input = json!({
5299                "capabilities": [
5300                    {
5301                        "protocol": "fuchsia.serve.service",
5302                    },
5303                ],
5304                "environments": [
5305                    {
5306                        "name": "myenv",
5307                        "extends": "realm",
5308                        "debug": [
5309                            {
5310                                "protocol": "fuchsia.serve.service",
5311                                "from": "self",
5312                                "as": "my-service",
5313                            }
5314                        ],
5315                    },
5316                ],
5317            }),
5318            output = fdecl::Component {
5319                capabilities: Some(vec![
5320                    fdecl::Capability::Protocol(
5321                        fdecl::Protocol {
5322                            name : Some("fuchsia.serve.service".to_owned()),
5323                            source_path: Some("/svc/fuchsia.serve.service".to_owned()),
5324                            ..Default::default()
5325                        }
5326                    )
5327                ]),
5328                environments: Some(vec![
5329                    fdecl::Environment {
5330                        name: Some("myenv".to_string()),
5331                        extends: Some(fdecl::EnvironmentExtends::Realm),
5332                        debug_capabilities: Some(vec![
5333                            fdecl::DebugRegistration::Protocol( fdecl::DebugProtocolRegistration {
5334                                source_name: Some("fuchsia.serve.service".to_string()),
5335                                source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
5336                                target_name: Some("my-service".to_string()),
5337                                ..Default::default()
5338                            }),
5339                        ]),
5340                        resolvers: None,
5341                        runners: None,
5342                        stop_timeout_ms: None,
5343                        ..Default::default()
5344                    },
5345                ]),
5346                ..default_component_decl()
5347            },
5348        },
5349
5350
5351        test_compile_configuration_capability => {
5352            input = json!({
5353                "capabilities": [
5354                    {
5355                        "config": "fuchsia.config.true",
5356                        "type": "bool",
5357                        "value": true,
5358                    },
5359                    {
5360                        "config": "fuchsia.config.false",
5361                        "type": "bool",
5362                        "value": false,
5363                    },
5364                ],
5365            }),
5366            output = fdecl::Component {
5367                capabilities: Some(vec![
5368                    fdecl::Capability::Config (
5369                        fdecl::Configuration {
5370                            name: Some("fuchsia.config.true".to_string()),
5371                            value: Some(fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Bool(true))),
5372                            ..Default::default()
5373                        }),
5374                    fdecl::Capability::Config (
5375                        fdecl::Configuration {
5376                            name: Some("fuchsia.config.false".to_string()),
5377                            value: Some(fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Bool(false))),
5378                            ..Default::default()
5379                        }),
5380                ]),
5381                ..default_component_decl()
5382            },
5383        },
5384
5385        test_compile_all_sections => {
5386            input = json!({
5387                "program": {
5388                    "runner": "elf",
5389                    "binary": "bin/app",
5390                },
5391                "use": [
5392                    { "protocol": "LegacyCoolFonts", "path": "/svc/fuchsia.fonts.LegacyProvider" },
5393                    { "protocol": [ "ReallyGoodFonts", "IWouldNeverUseTheseFonts"]},
5394                    { "protocol":  "DebugProtocol", "from": "debug"},
5395                ],
5396                "expose": [
5397                    { "directory": "blobfs", "from": "self", "rights": ["r*"]},
5398                ],
5399                "offer": [
5400                    {
5401                        "protocol": "fuchsia.logger.LegacyLog",
5402                        "from": "#logger",
5403                        "to": [ "#netstack", "#modular" ],
5404                        "dependency": "weak"
5405                    },
5406                ],
5407                "children": [
5408                    {
5409                        "name": "logger",
5410                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
5411                    },
5412                    {
5413                        "name": "netstack",
5414                        "url": "fuchsia-pkg://fuchsia.com/netstack/stable#meta/netstack.cm",
5415                    },
5416                ],
5417                "collections": [
5418                    {
5419                        "name": "modular",
5420                        "durability": "transient",
5421                    },
5422                ],
5423                "capabilities": [
5424                    {
5425                        "directory": "blobfs",
5426                        "path": "/volumes/blobfs",
5427                        "rights": [ "r*" ],
5428                    },
5429                    {
5430                        "runner": "myrunner",
5431                        "path": "/runner",
5432                    },
5433                    {
5434                        "protocol": "fuchsia.serve.service",
5435                    }
5436                ],
5437                "facets": {
5438                    "author": "Fuchsia",
5439                    "year": "2018",
5440                },
5441                "environments": [
5442                    {
5443                        "name": "myenv",
5444                        "extends": "realm",
5445                        "debug": [
5446                            {
5447                                "protocol": "fuchsia.serve.service",
5448                                "from": "self",
5449                                "as": "my-service",
5450                            },
5451                            {
5452                                "protocol": "fuchsia.logger.LegacyLog",
5453                                "from": "#logger",
5454                            }
5455                        ]
5456                    }
5457                ],
5458            }),
5459            output = fdecl::Component {
5460                program: Some(fdecl::Program {
5461                    runner: Some("elf".to_string()),
5462                    info: Some(fdata::Dictionary {
5463                        entries: Some(vec![fdata::DictionaryEntry {
5464                            key: "binary".to_string(),
5465                            value: Some(Box::new(fdata::DictionaryValue::Str("bin/app".to_string()))),
5466                        }]),
5467                        ..Default::default()
5468                    }),
5469                    ..Default::default()
5470                }),
5471                uses: Some(vec![
5472                    fdecl::Use::Protocol (
5473                        fdecl::UseProtocol {
5474                            dependency_type: Some(fdecl::DependencyType::Strong),
5475                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5476                            source_name: Some("LegacyCoolFonts".to_string()),
5477                            target_path: Some("/svc/fuchsia.fonts.LegacyProvider".to_string()),
5478                            availability: Some(fdecl::Availability::Required),
5479                            ..Default::default()
5480                        }
5481                    ),
5482                    fdecl::Use::Protocol (
5483                        fdecl::UseProtocol {
5484                            dependency_type: Some(fdecl::DependencyType::Strong),
5485                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5486                            source_name: Some("ReallyGoodFonts".to_string()),
5487                            target_path: Some("/svc/ReallyGoodFonts".to_string()),
5488                            availability: Some(fdecl::Availability::Required),
5489                            ..Default::default()
5490                        }
5491                    ),
5492                    fdecl::Use::Protocol (
5493                        fdecl::UseProtocol {
5494                            dependency_type: Some(fdecl::DependencyType::Strong),
5495                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5496                            source_name: Some("IWouldNeverUseTheseFonts".to_string()),
5497                            target_path: Some("/svc/IWouldNeverUseTheseFonts".to_string()),
5498                            availability: Some(fdecl::Availability::Required),
5499                            ..Default::default()
5500                        }
5501                    ),
5502                    fdecl::Use::Protocol (
5503                        fdecl::UseProtocol {
5504                            dependency_type: Some(fdecl::DependencyType::Strong),
5505                            source: Some(fdecl::Ref::Debug(fdecl::DebugRef {})),
5506                            source_name: Some("DebugProtocol".to_string()),
5507                            target_path: Some("/svc/DebugProtocol".to_string()),
5508                            availability: Some(fdecl::Availability::Required),
5509                            ..Default::default()
5510                        }
5511                    ),
5512                ]),
5513                exposes: Some(vec![
5514                    fdecl::Expose::Directory (
5515                        fdecl::ExposeDirectory {
5516                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
5517                            source_name: Some("blobfs".to_string()),
5518                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5519                            target_name: Some("blobfs".to_string()),
5520                            rights: Some(
5521                                fio::Operations::CONNECT | fio::Operations::ENUMERATE |
5522                                fio::Operations::TRAVERSE | fio::Operations::READ_BYTES |
5523                                fio::Operations::GET_ATTRIBUTES
5524                            ),
5525                            subdir: None,
5526                            availability: Some(fdecl::Availability::Required),
5527                            ..Default::default()
5528                        }
5529                    ),
5530                ]),
5531                offers: Some(vec![
5532                    fdecl::Offer::Protocol (
5533                        fdecl::OfferProtocol {
5534                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5535                                name: "logger".to_string(),
5536                                collection: None,
5537                            })),
5538                            source_name: Some("fuchsia.logger.LegacyLog".to_string()),
5539                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
5540                                name: "netstack".to_string(),
5541                                collection: None,
5542                            })),
5543                            target_name: Some("fuchsia.logger.LegacyLog".to_string()),
5544                            dependency_type: Some(fdecl::DependencyType::Weak),
5545                            availability: Some(fdecl::Availability::Required),
5546                            ..Default::default()
5547                        }
5548                    ),
5549                    fdecl::Offer::Protocol (
5550                        fdecl::OfferProtocol {
5551                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5552                                name: "logger".to_string(),
5553                                collection: None,
5554                            })),
5555                            source_name: Some("fuchsia.logger.LegacyLog".to_string()),
5556                            target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
5557                                name: "modular".to_string(),
5558                            })),
5559                            target_name: Some("fuchsia.logger.LegacyLog".to_string()),
5560                            dependency_type: Some(fdecl::DependencyType::Weak),
5561                            availability: Some(fdecl::Availability::Required),
5562                            ..Default::default()
5563                        }
5564                    ),
5565                ]),
5566                capabilities: Some(vec![
5567                    fdecl::Capability::Directory (
5568                        fdecl::Directory {
5569                            name: Some("blobfs".to_string()),
5570                            source_path: Some("/volumes/blobfs".to_string()),
5571                            rights: Some(fio::Operations::CONNECT | fio::Operations::ENUMERATE |
5572                                fio::Operations::TRAVERSE | fio::Operations::READ_BYTES |
5573                                fio::Operations::GET_ATTRIBUTES
5574                            ),
5575                            ..Default::default()
5576                        }
5577                    ),
5578                    fdecl::Capability::Runner (
5579                        fdecl::Runner {
5580                            name: Some("myrunner".to_string()),
5581                            source_path: Some("/runner".to_string()),
5582                            ..Default::default()
5583                        }
5584                    ),
5585                    fdecl::Capability::Protocol(
5586                        fdecl::Protocol {
5587                            name : Some("fuchsia.serve.service".to_owned()),
5588                            source_path: Some("/svc/fuchsia.serve.service".to_owned()),
5589                            ..Default::default()
5590                        }
5591                    )
5592                ]),
5593                children: Some(vec![
5594                    fdecl::Child {
5595                        name: Some("logger".to_string()),
5596                        url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
5597                        startup: Some(fdecl::StartupMode::Lazy),
5598                        environment: None,
5599                        on_terminate: None,
5600                        ..Default::default()
5601                    },
5602                    fdecl::Child {
5603                        name: Some("netstack".to_string()),
5604                        url: Some("fuchsia-pkg://fuchsia.com/netstack/stable#meta/netstack.cm".to_string()),
5605                        startup: Some(fdecl::StartupMode::Lazy),
5606                        environment: None,
5607                        on_terminate: None,
5608                        ..Default::default()
5609                    },
5610                ]),
5611                collections: Some(vec![
5612                    fdecl::Collection {
5613                        name: Some("modular".to_string()),
5614                        durability: Some(fdecl::Durability::Transient),
5615                        environment: None,
5616                        allowed_offers: None,
5617                        ..Default::default()
5618                    }
5619                ]),
5620                environments: Some(vec![
5621                    fdecl::Environment {
5622                        name: Some("myenv".to_string()),
5623                        extends: Some(fdecl::EnvironmentExtends::Realm),
5624                        runners: None,
5625                        resolvers: None,
5626                        stop_timeout_ms: None,
5627                        debug_capabilities: Some(vec![
5628                            fdecl::DebugRegistration::Protocol( fdecl::DebugProtocolRegistration {
5629                                source_name: Some("fuchsia.serve.service".to_string()),
5630                                source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
5631                                target_name: Some("my-service".to_string()),
5632                                ..Default::default()
5633                            }),
5634                            fdecl::DebugRegistration::Protocol( fdecl::DebugProtocolRegistration {
5635                                source_name: Some("fuchsia.logger.LegacyLog".to_string()),
5636                                source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5637                                    name: "logger".to_string(),
5638                                    collection: None,
5639                                })),
5640                                target_name: Some("fuchsia.logger.LegacyLog".to_string()),
5641                                ..Default::default()
5642                            }),
5643                        ]),
5644                        ..Default::default()
5645                    }
5646                ]),
5647                facets: Some(fdata::Dictionary {
5648                        entries: Some(vec![
5649                            fdata::DictionaryEntry {
5650                                key: "author".to_string(),
5651                                value: Some(Box::new(fdata::DictionaryValue::Str("Fuchsia".to_string()))),
5652                            },
5653                            fdata::DictionaryEntry {
5654                                key: "year".to_string(),
5655                                value: Some(Box::new(fdata::DictionaryValue::Str("2018".to_string()))),
5656                            },
5657                        ]),
5658                        ..Default::default()
5659                    }),
5660                ..Default::default()
5661            },
5662        },
5663    }
5664
5665    #[test]
5666    fn test_maybe_generate_specialization_from_all() {
5667        let offer = create_offer(
5668            "fuchsia.logger.LegacyLog",
5669            OneOrMany::One(OfferFromRef::Parent {}),
5670            OneOrMany::One(OfferToRef::All),
5671        );
5672
5673        let mut offer_set = vec![create_offer(
5674            "fuchsia.logger.LogSink",
5675            OneOrMany::One(OfferFromRef::Parent {}),
5676            OneOrMany::One(OfferToRef::All),
5677        )];
5678
5679        let result = maybe_generate_direct_offer_from_all(
5680            &offer,
5681            &offer_set,
5682            &Name::from_str("something").unwrap(),
5683        );
5684
5685        assert_matches!(&result[..], [Offer {protocol, from, to, ..}] => {
5686            assert_eq!(
5687                protocol,
5688                &Some(OneOrMany::One(Name::from_str("fuchsia.logger.LegacyLog").unwrap())),
5689            );
5690            assert_eq!(from, &OneOrMany::One(OfferFromRef::Parent {}));
5691            assert_eq!(
5692                to,
5693                &OneOrMany::One(OfferToRef::Named(Name::from_str("something").unwrap())),
5694            );
5695        });
5696
5697        offer_set.push(create_offer(
5698            "fuchsia.inspect.InspectSink",
5699            OneOrMany::One(OfferFromRef::Parent {}),
5700            OneOrMany::One(OfferToRef::Named(Name::from_str("something").unwrap())),
5701        ));
5702
5703        let result = maybe_generate_direct_offer_from_all(
5704            &offer,
5705            &offer_set,
5706            &Name::from_str("something").unwrap(),
5707        );
5708
5709        assert_matches!(&result[..], [Offer {protocol, from, to, ..}] => {
5710            assert_eq!(
5711                protocol,
5712                &Some(OneOrMany::One(Name::from_str("fuchsia.logger.LegacyLog").unwrap())),
5713            );
5714            assert_eq!(from, &OneOrMany::One(OfferFromRef::Parent {}));
5715            assert_eq!(
5716                to,
5717                &OneOrMany::One(OfferToRef::Named(Name::from_str("something").unwrap())),
5718            );
5719        });
5720
5721        offer_set.push(create_offer(
5722            "fuchsia.logger.LegacyLog",
5723            OneOrMany::One(OfferFromRef::Parent {}),
5724            OneOrMany::One(OfferToRef::Named(Name::from_str("something").unwrap())),
5725        ));
5726
5727        assert!(
5728            maybe_generate_direct_offer_from_all(
5729                &offer,
5730                &offer_set,
5731                &Name::from_str("something").unwrap()
5732            )
5733            .is_empty()
5734        );
5735    }
5736
5737    #[test]
5738    fn test_expose_void_service_capability() {
5739        let input = must_parse_cml!({
5740            "expose": [
5741                {
5742                    "service": "fuchsia.foo.Bar",
5743                    "from": [ "#non_existent_child" ],
5744                    "source_availability": "unknown",
5745                },
5746            ],
5747        });
5748        let result = compile(&input, CompileOptions::default());
5749        assert_matches!(result, Ok(_));
5750    }
5751
5752    /// Different availabilities aggregated by several service expose declarations is an error.
5753    #[test]
5754    fn test_aggregated_capabilities_must_use_same_availability_expose() {
5755        // Same availability.
5756        let input = must_parse_cml!({
5757            "expose": [
5758                {
5759                    "service": "fuchsia.foo.Bar",
5760                    "from": [ "#a", "#b" ],
5761                    "availability": "optional",
5762                },
5763            ],
5764            "collections": [
5765                {
5766                    "name": "a",
5767                    "durability": "transient",
5768                },
5769                {
5770                    "name": "b",
5771                    "durability": "transient",
5772                },
5773            ],
5774        });
5775        let result = compile(&input, CompileOptions::default());
5776        assert_matches!(result, Ok(_));
5777
5778        // Different availability.
5779        let input = must_parse_cml!({
5780            "expose": [
5781                {
5782                    "service": "fuchsia.foo.Bar",
5783                    "from": [ "#a", "#non_existent" ],
5784                    "source_availability": "unknown",
5785                },
5786            ],
5787            "collections": [
5788                {
5789                    "name": "a",
5790                    "durability": "transient",
5791                },
5792            ],
5793        });
5794        let result = compile(&input, CompileOptions::default());
5795        assert_matches!(
5796            result,
5797            Err(Error::FidlValidator  { errs: ErrorList { errs } })
5798            if matches!(
5799                &errs[..],
5800                [
5801                    CmFidlError::DifferentAvailabilityInAggregation(AvailabilityList(availabilities)),
5802                ]
5803                if matches!(
5804                    &availabilities[..],
5805                    [ fdecl::Availability::Required, fdecl::Availability::Optional, ]
5806                )
5807            )
5808        );
5809    }
5810
5811    #[test]
5812    fn test_aggregated_capabilities_must_use_same_availability_offer() {
5813        // Same availability.
5814        let input = must_parse_cml!({
5815            "offer": [
5816                {
5817                    "service": "fuchsia.foo.Bar",
5818                    "from": [ "#a", "#b" ],
5819                    "to": "#c",
5820                    "availability": "optional",
5821                },
5822            ],
5823            "collections": [
5824                {
5825                    "name": "a",
5826                    "durability": "transient",
5827                },
5828                {
5829                    "name": "b",
5830                    "durability": "transient",
5831                },
5832            ],
5833            "children": [
5834                {
5835                    "name": "c",
5836                    "url": "fuchsia-pkg://fuchsia.com/c/c#meta/c.cm",
5837                },
5838            ],
5839        });
5840        let result = compile(&input, CompileOptions::default());
5841        assert_matches!(result, Ok(_));
5842
5843        // Different availability.
5844        let input = must_parse_cml!({
5845            "offer": [
5846                {
5847                    "service": "fuchsia.foo.Bar",
5848                    "from": [ "#a", "#non_existent" ],
5849                    "to": "#c",
5850                    "source_availability": "unknown",
5851                },
5852            ],
5853            "collections": [
5854                {
5855                    "name": "a",
5856                    "durability": "transient",
5857                },
5858            ],
5859            "children": [
5860                {
5861                    "name": "c",
5862                    "url": "fuchsia-pkg://fuchsia.com/c/c#meta/c.cm",
5863                },
5864            ],
5865        });
5866        let result = compile(&input, CompileOptions::default());
5867        assert_matches!(
5868            result,
5869            Err(Error::FidlValidator  { errs: ErrorList { errs } })
5870            if matches!(
5871                &errs[..],
5872                [
5873                    CmFidlError::DifferentAvailabilityInAggregation(AvailabilityList(availabilities)),
5874                ]
5875                if matches!(
5876                    &availabilities[..],
5877                    [ fdecl::Availability::Required, fdecl::Availability::Optional, ]
5878                )
5879            )
5880        );
5881    }
5882
5883    #[test]
5884    fn test_compile_offer_to_all_exact_duplicate_disallowed() {
5885        let input = must_parse_cml!({
5886            "children": [
5887                {
5888                    "name": "logger",
5889                    "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
5890                },
5891            ],
5892            "offer": [
5893                {
5894                    "protocol": "fuchsia.logger.LogSink",
5895                    "from": "parent",
5896                    "to": "all",
5897                },
5898                {
5899                    "protocol": "fuchsia.logger.LogSink",
5900                    "from": "parent",
5901                    "to": "all",
5902                },
5903            ],
5904        });
5905        assert_matches!(
5906            compile(&input, CompileOptions::default()),
5907            Err(Error::Validate { err, .. })
5908            if &err == "Protocol(s) [\"fuchsia.logger.LogSink\"] offered to \"all\" multiple times"
5909        );
5910    }
5911
5912    #[test]
5913    fn test_compile_use_config() {
5914        let input = must_parse_cml!({
5915            "use": [
5916                    {
5917                        "config": "fuchsia.config.Config",
5918                        "key" : "my_config",
5919                        "type": "bool",
5920                    }
5921            ],
5922        });
5923        let options = CompileOptions::new().config_package_path("fake.cvf");
5924        let actual = compile(&input, options).unwrap();
5925        let type_ = fdecl::ConfigType {
5926            layout: fdecl::ConfigTypeLayout::Bool,
5927            parameters: Some(vec![]),
5928            constraints: vec![],
5929        };
5930        assert_eq!(
5931            actual.uses.unwrap(),
5932            vec![fdecl::Use::Config(fdecl::UseConfiguration {
5933                source_name: Some("fuchsia.config.Config".to_string()),
5934                source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5935                target_name: Some("my_config".to_string()),
5936                availability: Some(fdecl::Availability::Required),
5937                type_: Some(type_.clone()),
5938                ..Default::default()
5939            })]
5940        );
5941        assert_eq!(
5942            actual.config.unwrap().fields.unwrap(),
5943            [fdecl::ConfigField {
5944                key: Some("my_config".to_string()),
5945                type_: Some(type_),
5946                mutability: Some(fdecl::ConfigMutability::default()),
5947                ..Default::default()
5948            }]
5949            .to_vec(),
5950        );
5951    }
5952
5953    #[test]
5954    fn test_compile_use_config_optional_bad_type() {
5955        let input = must_parse_cml!({
5956            "use": [
5957                    {
5958                        "config": "fuchsia.config.Config",
5959                        "key" : "my_config",
5960                        "type": "bool",
5961                        "availability": "optional",
5962                    }
5963            ],
5964        "config": {
5965            "my_config": { "type": "int8"},
5966        }
5967        });
5968        let options = CompileOptions::new().config_package_path("fake.cvf");
5969        assert_matches!(
5970            compile(&input, options),
5971            Err(Error::Validate { err, .. })
5972            if &err == "Use and config block differ on type for key 'my_config'"
5973        );
5974    }
5975
5976    #[test]
5977    fn test_config_source_from_package() {
5978        let input = must_parse_cml!({
5979            "use": [
5980                    {
5981                        "config": "fuchsia.config.Config",
5982                        "key" : "my_config",
5983                        "type": "bool",
5984                        "availability": "optional",
5985                    }
5986            ],
5987        "config": {
5988            "my_config": { "type": "bool"},
5989        }
5990        });
5991        let options = CompileOptions::new().config_package_path("fake.cvf");
5992        let decl = compile(&input, options).unwrap();
5993        let config = decl.config.unwrap();
5994        assert_eq!(
5995            config.value_source,
5996            Some(fdecl::ConfigValueSource::PackagePath("fake.cvf".into()))
5997        );
5998    }
5999
6000    #[test]
6001    fn test_config_source_from_capabilities() {
6002        let input = must_parse_cml!({
6003            "use": [
6004                    {
6005                        "config": "fuchsia.config.Config",
6006                        "key" : "my_config",
6007                        "type": "bool",
6008                    }
6009            ],
6010        });
6011        let options = CompileOptions::new().config_package_path("fake.cvf");
6012        let decl = compile(&input, options).unwrap();
6013        let config = decl.config.unwrap();
6014        assert_eq!(
6015            config.value_source,
6016            Some(
6017                fdecl::ConfigValueSource::Capabilities(fdecl::ConfigSourceCapabilities::default())
6018            )
6019        );
6020    }
6021
6022    #[test]
6023    fn test_config_default() {
6024        let input = must_parse_cml!({
6025            "use": [
6026                    {
6027                        "config": "fuchsia.config.Config",
6028                        "key" : "my_config",
6029                        "type": "bool",
6030                        "availability": "optional",
6031                        "default": true
6032                    }
6033            ],
6034        });
6035        let options = CompileOptions::new().config_package_path("fake.cvf");
6036        let decl = compile(&input, options).unwrap();
6037        assert_matches!(
6038            decl.uses.as_ref().unwrap()[0],
6039            fdecl::Use::Config(fdecl::UseConfiguration {
6040                default: Some(fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Bool(true))),
6041                ..
6042            })
6043        );
6044    }
6045
6046    #[test]
6047    fn test_config_default_bad_type() {
6048        let input = must_parse_cml!({
6049            "use": [
6050                    {
6051                        "config": "fuchsia.config.Config",
6052                        "key" : "my_config",
6053                        "type": "bool",
6054                        "availability": "optional",
6055                        "default": 5
6056                    }
6057            ],
6058        });
6059        let options = CompileOptions::new().config_package_path("fake.cvf");
6060        assert_matches!(compile(&input, options), Err(Error::InvalidArgs(_)));
6061    }
6062
6063    #[test]
6064    fn test_compile_protocol_delivery_type() {
6065        let input = must_parse_cml!({
6066            "capabilities": [
6067                {
6068                    "protocol": "fuchsia.echo.Echo",
6069                    "delivery": "on_readable",
6070                }
6071            ],
6072        });
6073        let features = FeatureSet::from(vec![Feature::DeliveryType]);
6074        let options = CompileOptions::new().features(&features);
6075        let decl = compile(&input, options).unwrap();
6076        assert_matches!(
6077            decl.capabilities.as_ref().unwrap()[0],
6078            fdecl::Capability::Protocol(fdecl::Protocol {
6079                delivery: Some(fdecl::DeliveryType::OnReadable),
6080                ..
6081            })
6082        );
6083    }
6084
6085    #[test]
6086    fn test_compile_protocol_setting_delivery_type_requires_feature_flag() {
6087        let input = must_parse_cml!({
6088            "capabilities": [
6089                {
6090                    "protocol": "fuchsia.echo.Echo",
6091                    "delivery": "on_readable",
6092                }
6093            ],
6094        });
6095        assert_matches!(
6096            compile(&input, CompileOptions::new()),
6097            Err(Error::RestrictedFeature(feature))
6098            if feature == "delivery_type"
6099        );
6100    }
6101}