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