Skip to main content

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