cml/
translate.rs

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