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