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, BorrowedName, 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<&BorrowedName> =
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<&BorrowedName>,
266    all_children: &BTreeSet<&BorrowedName>,
267    all_collections: &BTreeSet<&BorrowedName>,
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.to_string()),
288                    #[cfg(fuchsia_api_level_at_least = "25")]
289                    source_dictionary: source_dictionary.clone(),
290                    target_path: Some(target_path.to_string()),
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.to_string()),
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<&BorrowedName> = 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<&BorrowedName>,
460    all_collections: &BTreeSet<&BorrowedName>,
461    all_children: &BTreeSet<&BorrowedName>,
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.to_string()),
488                        #[cfg(fuchsia_api_level_at_least = "25")]
489                        source_dictionary,
490                        target_name: Some(target_name.to_string()),
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.to_string()),
518                    #[cfg(fuchsia_api_level_at_least = "25")]
519                    source_dictionary,
520                    target_name: Some(target_name.to_string()),
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.to_string()),
548                    #[cfg(fuchsia_api_level_at_least = "25")]
549                    source_dictionary,
550                    target_name: Some(target_name.to_string()),
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.to_string()),
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.to_string()),
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.to_string()),
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.to_string()),
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.to_string()),
623                        source_dictionary,
624                        target_name: Some(target_name.to_string()),
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.to_string()),
661                        #[cfg(fuchsia_api_level_at_least = "25")]
662                        source_dictionary,
663                        target: Some(target.clone()),
664                        target_name: Some(target_name.to_string()),
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<&BorrowedName>,
705    all_children: &BTreeSet<&BorrowedName>,
706    all_collections: &BTreeSet<&BorrowedName>,
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.as_str()) =>
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.as_str()) =>
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.as_str()) =>
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: &BorrowedName,
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.into());
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.into()));
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<&BorrowedName>,
804    collections: &BTreeSet<&BorrowedName>,
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<&BorrowedName>,
832    all_children: &BTreeSet<&BorrowedName>,
833    all_collections: &BTreeSet<&BorrowedName>,
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.to_string()),
861                    #[cfg(fuchsia_api_level_at_least = "25")]
862                    source_dictionary,
863                    target: Some(target),
864                    target_name: Some(target_name.to_string()),
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.to_string()),
896                    #[cfg(fuchsia_api_level_at_least = "25")]
897                    source_dictionary,
898                    target: Some(target),
899                    target_name: Some(target_name.to_string()),
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.to_string()),
930                    #[cfg(fuchsia_api_level_at_least = "25")]
931                    source_dictionary,
932                    target: Some(target),
933                    target_name: Some(target_name.to_string()),
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.to_string()),
966                    target: Some(target),
967                    target_name: Some(target_name.to_string()),
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.to_string()),
985                    #[cfg(fuchsia_api_level_at_least = "25")]
986                    source_dictionary,
987                    target: Some(target),
988                    target_name: Some(target_name.to_string()),
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.to_string()),
1005                    #[cfg(fuchsia_api_level_at_least = "25")]
1006                    source_dictionary,
1007                    target: Some(target),
1008                    target_name: Some(target_name.to_string()),
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.to_string()),
1039                    target: Some(target),
1040                    target_name: Some(target_name.to_string()),
1041                    scope: match scopes {
1042                        Some(values) => {
1043                            let mut output = vec![];
1044                            for value in &values {
1045                                static EMPTY_SET: BTreeSet<&BorrowedName> = 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.to_string()),
1093                        source_dictionary,
1094                        target: Some(target),
1095                        target_name: Some(target_name.to_string()),
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.to_string()),
1135                        target: Some(target),
1136                        target_name: Some(target_name.to_string()),
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<&BorrowedName>,
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<&BorrowedName>,
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()
1457                } else {
1458                    target_name
1459                };
1460                out_capabilities.push(fdecl::DebugRegistration::Protocol(
1461                    fdecl::DebugProtocolRegistration {
1462                        source: Some(source.clone()),
1463                        source_name: Some(source_name.to_string()),
1464                        target_name: Some(target_name.to_string()),
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<&BorrowedName>,
1478    all_children_names: &BTreeSet<&BorrowedName>,
1479    all_collection_names: Option<&BTreeSet<&BorrowedName>>,
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.as_ref()) {
1488                fdecl::Ref::Capability(fdecl::CapabilityRef { name: name.to_string() })
1489            } else if all_children_names.contains(&name.as_ref()) {
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.as_ref())
1493            {
1494                fdecl::Ref::Collection(fdecl::CollectionRef { name: name.to_string() })
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<&BorrowedName>>,
1568    all_collections: Option<&BTreeSet<&BorrowedName>>,
1569) -> Result<(fdecl::Ref, Option<String>), Error> {
1570    let ref_ = match reference {
1571        ExposeFromRef::Named(name) => {
1572            if all_capability_names.is_some()
1573                && all_capability_names.unwrap().contains(&name.as_ref())
1574            {
1575                fdecl::Ref::Capability(fdecl::CapabilityRef { name: name.to_string() })
1576            } else if all_collections.is_some() && all_collections.unwrap().contains(&name.as_ref())
1577            {
1578                fdecl::Ref::Collection(fdecl::CollectionRef { name: name.to_string() })
1579            } else {
1580                fdecl::Ref::Child(fdecl::ChildRef { name: name.to_string(), collection: None })
1581            }
1582        }
1583        ExposeFromRef::Framework => fdecl::Ref::Framework(fdecl::FrameworkRef {}),
1584        ExposeFromRef::Self_ => fdecl::Ref::Self_(fdecl::SelfRef {}),
1585        ExposeFromRef::Void => fdecl::Ref::VoidType(fdecl::VoidRef {}),
1586        ExposeFromRef::Dictionary(d) => {
1587            return dictionary_ref_to_source(&d);
1588        }
1589    };
1590    Ok((ref_, None))
1591}
1592
1593fn extract_single_expose_source(
1594    options: &CompileOptions<'_>,
1595    in_obj: &Expose,
1596    all_capability_names: Option<&BTreeSet<&BorrowedName>>,
1597) -> Result<(fdecl::Ref, Option<String>), Error> {
1598    match &in_obj.from {
1599        OneOrMany::One(reference) => {
1600            expose_source_from_ref(options, &reference, all_capability_names, None)
1601        }
1602        OneOrMany::Many(many) => {
1603            return Err(Error::internal(format!(
1604                "multiple unexpected \"from\" clauses for \"expose\": {:?}",
1605                many
1606            )))
1607        }
1608    }
1609}
1610
1611fn extract_all_expose_sources(
1612    options: &CompileOptions<'_>,
1613    in_obj: &Expose,
1614    all_collections: Option<&BTreeSet<&BorrowedName>>,
1615) -> Result<Vec<(fdecl::Ref, Option<String>)>, Error> {
1616    in_obj.from.iter().map(|e| expose_source_from_ref(options, e, None, all_collections)).collect()
1617}
1618
1619fn extract_offer_rights(in_obj: &Offer) -> Result<Option<fio::Operations>, Error> {
1620    match in_obj.rights.as_ref() {
1621        Some(rights_tokens) => {
1622            let mut rights = Vec::new();
1623            for token in rights_tokens.0.iter() {
1624                rights.append(&mut token.expand())
1625            }
1626            if rights.is_empty() {
1627                return Err(Error::missing_rights("Rights provided to offer are not well formed."));
1628            }
1629            let mut seen_rights = BTreeSet::new();
1630            let mut operations: fio::Operations = fio::Operations::empty();
1631            for right in rights.iter() {
1632                if seen_rights.contains(&right) {
1633                    return Err(Error::duplicate_rights(
1634                        "Rights provided to offer are not well formed.",
1635                    ));
1636                }
1637                seen_rights.insert(right);
1638                operations |= *right;
1639            }
1640
1641            Ok(Some(operations))
1642        }
1643        // Unlike use rights, offer rights can take a None value
1644        None => Ok(None),
1645    }
1646}
1647
1648fn extract_single_offer_source<T>(
1649    options: &CompileOptions<'_>,
1650    in_obj: &T,
1651    all_capability_names: Option<&BTreeSet<&BorrowedName>>,
1652) -> Result<(fdecl::Ref, Option<String>), Error>
1653where
1654    T: FromClause,
1655{
1656    match in_obj.from_() {
1657        OneOrMany::One(reference) => {
1658            any_ref_to_decl(options, reference, all_capability_names, None)
1659        }
1660        many => {
1661            return Err(Error::internal(format!(
1662                "multiple unexpected \"from\" clauses for \"offer\": {}",
1663                many
1664            )))
1665        }
1666    }
1667}
1668
1669fn extract_all_offer_sources<T: FromClause>(
1670    options: &CompileOptions<'_>,
1671    in_obj: &T,
1672    all_capability_names: &BTreeSet<&BorrowedName>,
1673    all_collections: &BTreeSet<&BorrowedName>,
1674) -> Result<Vec<(fdecl::Ref, Option<String>)>, Error> {
1675    in_obj
1676        .from_()
1677        .into_iter()
1678        .map(|r| {
1679            any_ref_to_decl(options, r.clone(), Some(all_capability_names), Some(all_collections))
1680        })
1681        .collect()
1682}
1683
1684fn translate_target_ref(
1685    options: &CompileOptions<'_>,
1686    reference: AnyRef<'_>,
1687    all_children: &BTreeSet<&BorrowedName>,
1688    all_collections: &BTreeSet<&BorrowedName>,
1689    all_capabilities: &BTreeSet<&BorrowedName>,
1690) -> Result<fdecl::Ref, Error> {
1691    match reference {
1692        AnyRef::Named(name) if all_children.contains(name) => {
1693            Ok(fdecl::Ref::Child(fdecl::ChildRef { name: name.to_string(), collection: None }))
1694        }
1695        AnyRef::Named(name) if all_collections.contains(name) => {
1696            Ok(fdecl::Ref::Collection(fdecl::CollectionRef { name: name.to_string() }))
1697        }
1698        AnyRef::Named(name) if all_capabilities.contains(name) => {
1699            Ok(fdecl::Ref::Capability(fdecl::CapabilityRef { name: name.to_string() }))
1700        }
1701        AnyRef::OwnDictionary(name) if all_capabilities.contains(name) => {
1702            #[cfg(fuchsia_api_level_at_least = "25")]
1703            return Ok(fdecl::Ref::Capability(fdecl::CapabilityRef { name: name.to_string() }));
1704            #[cfg(fuchsia_api_level_less_than = "25")]
1705            return Err(Error::validate("dictionaries are not supported at this API level"));
1706        }
1707        AnyRef::Named(_) => Err(Error::internal(format!("dangling reference: \"{}\"", reference))),
1708        _ => Err(Error::internal(format!("invalid child reference: \"{}\"", reference))),
1709    }
1710}
1711
1712// Return a list of (source, source capability id, source dictionary, target,
1713// target capability id) expressed in the `offer`.
1714fn extract_offer_sources_and_targets<'a>(
1715    options: &CompileOptions<'_>,
1716    offer: &'a Offer,
1717    source_names: OneOrMany<&'a BorrowedName>,
1718    all_capability_names: &BTreeSet<&'a BorrowedName>,
1719    all_children: &BTreeSet<&'a BorrowedName>,
1720    all_collections: &BTreeSet<&'a BorrowedName>,
1721) -> Result<Vec<(fdecl::Ref, Option<String>, &'a BorrowedName, fdecl::Ref, &'a BorrowedName)>, Error>
1722{
1723    let mut out = vec![];
1724
1725    let sources = extract_all_offer_sources(options, offer, all_capability_names, all_collections)?;
1726    let target_names = all_target_capability_names(offer, offer)
1727        .ok_or_else(|| Error::internal("no capability".to_string()))?;
1728
1729    for (source, source_dictionary) in sources {
1730        for to in &offer.to {
1731            for target_name in &target_names {
1732                // When multiple source names are provided, there is no way to alias each one,
1733                // so we can assume source_name == target_name.  When one source name is provided,
1734                // source_name may be aliased to a different target_name, so we use
1735                // source_names[0] to obtain the source_name.
1736                let source_name = if source_names.len() == 1 {
1737                    source_names.iter().next().unwrap()
1738                } else {
1739                    target_name
1740                };
1741                let target = translate_target_ref(
1742                    options,
1743                    to.into(),
1744                    all_children,
1745                    all_collections,
1746                    all_capability_names,
1747                )?;
1748                out.push((
1749                    source.clone(),
1750                    source_dictionary.clone(),
1751                    *source_name,
1752                    target.clone(),
1753                    *target_name,
1754                ))
1755            }
1756        }
1757    }
1758    Ok(out)
1759}
1760
1761/// Return the target paths specified in the given use declaration.
1762fn all_target_use_paths<T, U>(in_obj: &T, to_obj: &U) -> Option<OneOrMany<Path>>
1763where
1764    T: CapabilityClause,
1765    U: PathClause,
1766{
1767    if let Some(n) = in_obj.service() {
1768        Some(svc_paths_from_names(n, to_obj))
1769    } else if let Some(n) = in_obj.protocol() {
1770        Some(svc_paths_from_names(n, to_obj))
1771    } else if let Some(_) = in_obj.directory() {
1772        let path = to_obj.path().expect("no path on use directory");
1773        Some(OneOrMany::One(path.clone()))
1774    } else if let Some(_) = in_obj.storage() {
1775        let path = to_obj.path().expect("no path on use storage");
1776        Some(OneOrMany::One(path.clone()))
1777    } else if let Some(_) = in_obj.event_stream() {
1778        let default_path = Path::new("/svc/fuchsia.component.EventStream").unwrap();
1779        let path = to_obj.path().unwrap_or(&default_path);
1780        Some(OneOrMany::One(path.clone()))
1781    } else {
1782        None
1783    }
1784}
1785
1786/// Returns the list of paths derived from a `use` declaration with `names` and `to_obj`. `to_obj`
1787/// must be a declaration that has a `path` clause.
1788fn svc_paths_from_names<T>(names: OneOrMany<&BorrowedName>, to_obj: &T) -> OneOrMany<Path>
1789where
1790    T: PathClause,
1791{
1792    match names {
1793        OneOrMany::One(n) => {
1794            if let Some(path) = to_obj.path() {
1795                OneOrMany::One(path.clone())
1796            } else {
1797                OneOrMany::One(format!("/svc/{}", n).parse().unwrap())
1798            }
1799        }
1800        OneOrMany::Many(v) => {
1801            let many = v.iter().map(|n| format!("/svc/{}", n).parse().unwrap()).collect();
1802            OneOrMany::Many(many)
1803        }
1804    }
1805}
1806
1807/// Return the single target path specified in the given use declaration.
1808fn one_target_use_path<T, U>(in_obj: &T, to_obj: &U) -> Result<Path, Error>
1809where
1810    T: CapabilityClause,
1811    U: PathClause,
1812{
1813    match all_target_use_paths(in_obj, to_obj) {
1814        Some(OneOrMany::One(target_name)) => Ok(target_name),
1815        Some(OneOrMany::Many(_)) => {
1816            Err(Error::internal("expecting one capability, but multiple provided"))
1817        }
1818        _ => Err(Error::internal("expecting one capability, but none provided")),
1819    }
1820}
1821
1822/// Return the target names or paths specified in the given capability.
1823fn all_target_capability_names<'a, T, U>(
1824    in_obj: &'a T,
1825    to_obj: &'a U,
1826) -> Option<OneOrMany<&'a BorrowedName>>
1827where
1828    T: CapabilityClause,
1829    U: AsClause + PathClause,
1830{
1831    if let Some(as_) = to_obj.r#as() {
1832        // We've already validated that when `as` is specified, only 1 source id exists.
1833        Some(OneOrMany::One(as_))
1834    } else {
1835        if let Some(n) = in_obj.service() {
1836            Some(n)
1837        } else if let Some(n) = in_obj.protocol() {
1838            Some(n)
1839        } else if let Some(n) = in_obj.directory() {
1840            Some(n)
1841        } else if let Some(n) = in_obj.storage() {
1842            Some(n)
1843        } else if let Some(n) = in_obj.runner() {
1844            Some(n)
1845        } else if let Some(n) = in_obj.resolver() {
1846            Some(n)
1847        } else if let Some(n) = in_obj.event_stream() {
1848            Some(n)
1849        } else if let Some(n) = in_obj.dictionary() {
1850            Some(n)
1851        } else if let Some(n) = in_obj.config() {
1852            Some(n)
1853        } else {
1854            None
1855        }
1856    }
1857}
1858
1859fn extract_expose_target(in_obj: &Expose) -> Result<fdecl::Ref, Error> {
1860    match &in_obj.to {
1861        Some(ExposeToRef::Parent) => Ok(fdecl::Ref::Parent(fdecl::ParentRef {})),
1862        Some(ExposeToRef::Framework) => Ok(fdecl::Ref::Framework(fdecl::FrameworkRef {})),
1863        None => Ok(fdecl::Ref::Parent(fdecl::ParentRef {})),
1864    }
1865}
1866
1867fn extract_environment_ref(r: Option<&EnvironmentRef>) -> Option<cm::Name> {
1868    r.map(|r| {
1869        let EnvironmentRef::Named(name) = r;
1870        name.clone()
1871    })
1872}
1873
1874pub fn translate_capabilities(
1875    options: &CompileOptions<'_>,
1876    capabilities_in: &Vec<Capability>,
1877    as_builtin: bool,
1878) -> Result<Vec<fdecl::Capability>, Error> {
1879    let mut out_capabilities = vec![];
1880    for capability in capabilities_in {
1881        if let Some(service) = &capability.service {
1882            for n in service.iter() {
1883                let source_path = match as_builtin {
1884                    true => None,
1885                    false => Some(
1886                        capability
1887                            .path
1888                            .clone()
1889                            .unwrap_or_else(|| format!("/svc/{}", n).parse().unwrap())
1890                            .into(),
1891                    ),
1892                };
1893                out_capabilities.push(fdecl::Capability::Service(fdecl::Service {
1894                    name: Some(n.clone().into()),
1895                    source_path,
1896                    ..Default::default()
1897                }));
1898            }
1899        } else if let Some(protocol) = &capability.protocol {
1900            for n in protocol.iter() {
1901                let source_path = match as_builtin {
1902                    true => None,
1903                    false => Some(
1904                        capability
1905                            .path
1906                            .clone()
1907                            .unwrap_or_else(|| format!("/svc/{}", n).parse().unwrap())
1908                            .into(),
1909                    ),
1910                };
1911                out_capabilities.push(fdecl::Capability::Protocol(fdecl::Protocol {
1912                    name: Some(n.clone().into()),
1913                    source_path,
1914                    #[cfg(fuchsia_api_level_at_least = "HEAD")]
1915                    delivery: capability.delivery.map(Into::into),
1916                    ..Default::default()
1917                }));
1918            }
1919        } else if let Some(n) = &capability.directory {
1920            let source_path = match as_builtin {
1921                true => None,
1922                false => {
1923                    Some(capability.path.as_ref().expect("missing source path").clone().into())
1924                }
1925            };
1926            let rights = extract_required_rights(capability, "capability")?;
1927            out_capabilities.push(fdecl::Capability::Directory(fdecl::Directory {
1928                name: Some(n.clone().into()),
1929                source_path,
1930                rights: Some(rights),
1931                ..Default::default()
1932            }));
1933        } else if let Some(n) = &capability.storage {
1934            if as_builtin {
1935                return Err(Error::internal(format!(
1936                    "built-in storage capabilities are not supported"
1937                )));
1938            }
1939            let backing_dir = capability
1940                .backing_dir
1941                .as_ref()
1942                .expect("storage has no path or backing_dir")
1943                .clone()
1944                .into();
1945
1946            let (source, _source_dictionary) =
1947                any_ref_to_decl(options, capability.from.as_ref().unwrap().into(), None, None)?;
1948            out_capabilities.push(fdecl::Capability::Storage(fdecl::Storage {
1949                name: Some(n.clone().into()),
1950                backing_dir: Some(backing_dir),
1951                subdir: capability.subdir.clone().map(Into::into),
1952                source: Some(source),
1953                storage_id: Some(
1954                    capability.storage_id.clone().expect("storage is missing storage_id").into(),
1955                ),
1956                ..Default::default()
1957            }));
1958        } else if let Some(n) = &capability.runner {
1959            let source_path = match as_builtin {
1960                true => None,
1961                false => {
1962                    Some(capability.path.as_ref().expect("missing source path").clone().into())
1963                }
1964            };
1965            out_capabilities.push(fdecl::Capability::Runner(fdecl::Runner {
1966                name: Some(n.clone().into()),
1967                source_path,
1968                ..Default::default()
1969            }));
1970        } else if let Some(n) = &capability.resolver {
1971            let source_path = match as_builtin {
1972                true => None,
1973                false => {
1974                    Some(capability.path.as_ref().expect("missing source path").clone().into())
1975                }
1976            };
1977            out_capabilities.push(fdecl::Capability::Resolver(fdecl::Resolver {
1978                name: Some(n.clone().into()),
1979                source_path,
1980                ..Default::default()
1981            }));
1982        } else if let Some(ns) = &capability.event_stream {
1983            if !as_builtin {
1984                return Err(Error::internal(format!(
1985                    "event_stream capabilities may only be declared as built-in capabilities"
1986                )));
1987            }
1988            for n in ns {
1989                out_capabilities.push(fdecl::Capability::EventStream(fdecl::EventStream {
1990                    name: Some(n.clone().into()),
1991                    ..Default::default()
1992                }));
1993            }
1994        } else if let Some(n) = &capability.dictionary {
1995            #[cfg(fuchsia_api_level_less_than = "25")]
1996            {
1997                return Err(Error::validate(format!(
1998                    "dictionary capabilities are not supported at this API level"
1999                )));
2000            }
2001            #[cfg(fuchsia_api_level_at_least = "25")]
2002            {
2003                out_capabilities.push(fdecl::Capability::Dictionary(fdecl::Dictionary {
2004                    name: Some(n.clone().into()),
2005                    source_path: capability.path.clone().map(Into::into),
2006                    ..Default::default()
2007                }));
2008            }
2009        } else if let Some(c) = &capability.config {
2010            #[cfg(fuchsia_api_level_less_than = "20")]
2011            {
2012                return Err(Error::validate(format!(
2013                    "configuration capabilities are not supported at this API level"
2014                )));
2015            }
2016            #[cfg(fuchsia_api_level_at_least = "20")]
2017            {
2018                let value = configuration_to_value(
2019                    c,
2020                    &capability,
2021                    &capability.config_type,
2022                    &capability.value,
2023                )?;
2024                out_capabilities.push(fdecl::Capability::Config(fdecl::Configuration {
2025                    name: Some(c.clone().into()),
2026                    value: Some(value),
2027                    ..Default::default()
2028                }));
2029            }
2030        } else {
2031            return Err(Error::internal(format!("no capability declaration recognized")));
2032        }
2033    }
2034    Ok(out_capabilities)
2035}
2036
2037pub fn extract_required_rights<T>(in_obj: &T, keyword: &str) -> Result<fio::Operations, Error>
2038where
2039    T: RightsClause,
2040{
2041    match in_obj.rights() {
2042        Some(rights_tokens) => {
2043            let mut rights = Vec::new();
2044            for token in rights_tokens.0.iter() {
2045                rights.append(&mut token.expand())
2046            }
2047            if rights.is_empty() {
2048                return Err(Error::missing_rights(format!(
2049                    "Rights provided to `{}` are not well formed.",
2050                    keyword
2051                )));
2052            }
2053            let mut seen_rights = BTreeSet::new();
2054            let mut operations: fio::Operations = fio::Operations::empty();
2055            for right in rights.iter() {
2056                if seen_rights.contains(&right) {
2057                    return Err(Error::duplicate_rights(format!(
2058                        "Rights provided to `{}` are not well formed.",
2059                        keyword
2060                    )));
2061                }
2062                seen_rights.insert(right);
2063                operations |= *right;
2064            }
2065
2066            Ok(operations)
2067        }
2068        None => Err(Error::internal(format!(
2069            "No `{}` rights provided but required for directories",
2070            keyword
2071        ))),
2072    }
2073}
2074
2075/// Takes an `AnyRef` and returns the `fdecl::Ref` equivalent and the dictionary path, if
2076/// the ref was a dictionary ref.
2077pub fn any_ref_to_decl(
2078    options: &CompileOptions<'_>,
2079    reference: AnyRef<'_>,
2080    all_capability_names: Option<&BTreeSet<&BorrowedName>>,
2081    all_collection_names: Option<&BTreeSet<&BorrowedName>>,
2082) -> Result<(fdecl::Ref, Option<String>), Error> {
2083    let ref_ = match reference {
2084        AnyRef::Named(name) => {
2085            if all_capability_names.is_some() && all_capability_names.unwrap().contains(name) {
2086                fdecl::Ref::Capability(fdecl::CapabilityRef { name: name.to_string() })
2087            } else if all_collection_names.is_some() && all_collection_names.unwrap().contains(name)
2088            {
2089                fdecl::Ref::Collection(fdecl::CollectionRef { name: name.to_string() })
2090            } else {
2091                fdecl::Ref::Child(fdecl::ChildRef { name: name.to_string(), collection: None })
2092            }
2093        }
2094        AnyRef::Framework => fdecl::Ref::Framework(fdecl::FrameworkRef {}),
2095        AnyRef::Debug => fdecl::Ref::Debug(fdecl::DebugRef {}),
2096        AnyRef::Parent => fdecl::Ref::Parent(fdecl::ParentRef {}),
2097        AnyRef::Self_ => fdecl::Ref::Self_(fdecl::SelfRef {}),
2098        AnyRef::Void => fdecl::Ref::VoidType(fdecl::VoidRef {}),
2099        AnyRef::Dictionary(d) => {
2100            return dictionary_ref_to_source(&d);
2101        }
2102        AnyRef::OwnDictionary(name) => {
2103            #[cfg(fuchsia_api_level_at_least = "25")]
2104            {
2105                fdecl::Ref::Capability(fdecl::CapabilityRef { name: name.to_string() })
2106            }
2107            #[cfg(fuchsia_api_level_less_than = "25")]
2108            return Err(Error::validate("dictionaries are not supported at this API level"));
2109        }
2110    };
2111    Ok((ref_, None))
2112}
2113
2114/// Takes a `DictionaryRef` and returns the `fdecl::Ref` equivalent and the dictionary path.
2115fn dictionary_ref_to_source(d: &DictionaryRef) -> Result<(fdecl::Ref, Option<String>), Error> {
2116    #[allow(unused)]
2117    let root = match &d.root {
2118        RootDictionaryRef::Named(name) => {
2119            fdecl::Ref::Child(fdecl::ChildRef { name: name.clone().into(), collection: None })
2120        }
2121        RootDictionaryRef::Parent => fdecl::Ref::Parent(fdecl::ParentRef {}),
2122        RootDictionaryRef::Self_ => fdecl::Ref::Self_(fdecl::SelfRef {}),
2123    };
2124
2125    #[cfg(fuchsia_api_level_at_least = "25")]
2126    return Ok((root, Some(d.path.to_string())));
2127    #[cfg(fuchsia_api_level_less_than = "25")]
2128    return Err(Error::validate("dictionaries are not supported at this API level"));
2129}
2130
2131fn configuration_to_value(
2132    name: &BorrowedName,
2133    capability: &Capability,
2134    config_type: &Option<ConfigType>,
2135    value: &Option<serde_json::Value>,
2136) -> Result<fdecl::ConfigValue, Error> {
2137    let Some(config_type) = config_type.as_ref() else {
2138        return Err(Error::InvalidArgs(format!(
2139            "Configuration field '{}' must have 'type' set",
2140            name
2141        )));
2142    };
2143    let Some(value) = value.as_ref() else {
2144        return Err(Error::InvalidArgs(format!(
2145            "Configuration field '{}' must have 'value' set",
2146            name
2147        )));
2148    };
2149
2150    let config_type = match config_type {
2151        ConfigType::Bool => cm_rust::ConfigValueType::Bool,
2152        ConfigType::Uint8 => cm_rust::ConfigValueType::Uint8,
2153        ConfigType::Uint16 => cm_rust::ConfigValueType::Uint16,
2154        ConfigType::Uint32 => cm_rust::ConfigValueType::Uint32,
2155        ConfigType::Uint64 => cm_rust::ConfigValueType::Uint64,
2156        ConfigType::Int8 => cm_rust::ConfigValueType::Int8,
2157        ConfigType::Int16 => cm_rust::ConfigValueType::Int16,
2158        ConfigType::Int32 => cm_rust::ConfigValueType::Int32,
2159        ConfigType::Int64 => cm_rust::ConfigValueType::Int64,
2160        ConfigType::String => {
2161            let Some(max_size) = capability.config_max_size else {
2162                return Err(Error::InvalidArgs(format!(
2163                    "Configuration field '{}' must have 'max_size' set",
2164                    name
2165                )));
2166            };
2167            cm_rust::ConfigValueType::String { max_size: max_size.into() }
2168        }
2169        ConfigType::Vector => {
2170            let Some(ref element) = capability.config_element_type else {
2171                return Err(Error::InvalidArgs(format!(
2172                    "Configuration field '{}' must have 'element_type' set",
2173                    name
2174                )));
2175            };
2176            let Some(max_count) = capability.config_max_count else {
2177                return Err(Error::InvalidArgs(format!(
2178                    "Configuration field '{}' must have 'max_count' set",
2179                    name
2180                )));
2181            };
2182            let nested_type = match element {
2183                ConfigNestedValueType::Bool { .. } => cm_rust::ConfigNestedValueType::Bool,
2184                ConfigNestedValueType::Uint8 { .. } => cm_rust::ConfigNestedValueType::Uint8,
2185                ConfigNestedValueType::Uint16 { .. } => cm_rust::ConfigNestedValueType::Uint16,
2186                ConfigNestedValueType::Uint32 { .. } => cm_rust::ConfigNestedValueType::Uint32,
2187                ConfigNestedValueType::Uint64 { .. } => cm_rust::ConfigNestedValueType::Uint64,
2188                ConfigNestedValueType::Int8 { .. } => cm_rust::ConfigNestedValueType::Int8,
2189                ConfigNestedValueType::Int16 { .. } => cm_rust::ConfigNestedValueType::Int16,
2190                ConfigNestedValueType::Int32 { .. } => cm_rust::ConfigNestedValueType::Int32,
2191                ConfigNestedValueType::Int64 { .. } => cm_rust::ConfigNestedValueType::Int64,
2192                ConfigNestedValueType::String { max_size } => {
2193                    cm_rust::ConfigNestedValueType::String { max_size: (*max_size).into() }
2194                }
2195            };
2196            cm_rust::ConfigValueType::Vector { max_count: max_count.into(), nested_type }
2197        }
2198    };
2199    let value = config_value_file::field::config_value_from_json_value(value, &config_type)
2200        .map_err(|e| Error::InvalidArgs(format!("Error parsing config '{}': {}", name, e)))?;
2201    Ok(value.native_into_fidl())
2202}
2203
2204#[cfg(test)]
2205pub mod test_util {
2206    /// Construct a CML [Document] from the provided JSON literal expression or panic if error.
2207    macro_rules! must_parse_cml {
2208        ($($input:tt)+) => {
2209            serde_json::from_str::<Document>(&json!($($input)+).to_string())
2210                .expect("deserialization failed")
2211        };
2212    }
2213    pub(crate) use must_parse_cml;
2214}
2215
2216#[cfg(test)]
2217mod tests {
2218    use super::*;
2219    use crate::error::Error;
2220    use crate::features::Feature;
2221    use crate::translate::test_util::must_parse_cml;
2222    use crate::{
2223        create_offer, AnyRef, AsClause, Capability, CapabilityClause, Child, Collection,
2224        DebugRegistration, Document, Environment, EnvironmentExtends, EnvironmentRef, Expose,
2225        ExposeFromRef, ExposeToRef, FromClause, Offer, OfferFromRef, OneOrMany, Path, PathClause,
2226        Program, ResolverRegistration, RightsClause, RunnerRegistration, Use, UseFromRef,
2227    };
2228    use assert_matches::assert_matches;
2229    use cm_fidl_validator::error::{AvailabilityList, DeclField, Error as CmFidlError, ErrorList};
2230    use cm_types::{self as cm, Name};
2231    use difference::Changeset;
2232    use serde_json::{json, Map, Value};
2233    use std::collections::BTreeSet;
2234    use std::convert::Into;
2235    use std::str::FromStr;
2236    use {
2237        fidl_fuchsia_component_decl as fdecl, fidl_fuchsia_data as fdata, fidl_fuchsia_io as fio,
2238    };
2239
2240    macro_rules! test_compile {
2241        (
2242            $(
2243                $(#[$m:meta])*
2244                $test_name:ident => {
2245                    $(features = $features:expr,)?
2246                    input = $input:expr,
2247                    output = $expected:expr,
2248                },
2249            )+
2250        ) => {
2251            $(
2252                $(#[$m])*
2253                #[test]
2254                fn $test_name() {
2255                    let input = serde_json::from_str(&$input.to_string()).expect("deserialization failed");
2256                    let options = CompileOptions::new().config_package_path("fake.cvf");
2257                    // Optionally specify features.
2258                    $(let features = $features; let options = options.features(&features);)?
2259                    let actual = compile(&input, options).expect("compilation failed");
2260                    if actual != $expected {
2261                        let e = format!("{:#?}", $expected);
2262                        let a = format!("{:#?}", actual);
2263                        panic!("{}", Changeset::new(&a, &e, "\n"));
2264                    }
2265                }
2266            )+
2267        };
2268    }
2269
2270    fn default_component_decl() -> fdecl::Component {
2271        fdecl::Component::default()
2272    }
2273
2274    test_compile! {
2275        test_compile_empty => {
2276            input = json!({}),
2277            output = default_component_decl(),
2278        },
2279
2280        test_compile_empty_includes => {
2281            input = json!({ "include": [] }),
2282            output = default_component_decl(),
2283        },
2284
2285        test_compile_offer_to_all_and_diff_sources => {
2286            input = json!({
2287                "children": [
2288                    {
2289                        "name": "logger",
2290                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
2291                    },
2292                ],
2293                "collections": [
2294                    {
2295                        "name": "coll",
2296                        "durability": "transient",
2297                    },
2298                ],
2299                "offer": [
2300                    {
2301                        "protocol": "fuchsia.logger.LogSink",
2302                        "from": "parent",
2303                        "to": "all",
2304                    },
2305                    {
2306                        "protocol": "fuchsia.logger.LogSink",
2307                        "from": "framework",
2308                        "to": "#logger",
2309                        "as": "LogSink2",
2310                    },
2311                ],
2312            }),
2313            output = fdecl::Component {
2314                offers: Some(vec![
2315                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2316                        source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef {})),
2317                        source_name: Some("fuchsia.logger.LogSink".into()),
2318                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2319                            name: "logger".into(),
2320                            collection: None,
2321                        })),
2322                        target_name: Some("LogSink2".into()),
2323                        dependency_type: Some(fdecl::DependencyType::Strong),
2324                        availability: Some(fdecl::Availability::Required),
2325                        ..Default::default()
2326                    }),
2327                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2328                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2329                        source_name: Some("fuchsia.logger.LogSink".into()),
2330                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2331                            name: "logger".into(),
2332                            collection: None,
2333                        })),
2334                        target_name: Some("fuchsia.logger.LogSink".into()),
2335                        dependency_type: Some(fdecl::DependencyType::Strong),
2336                        availability: Some(fdecl::Availability::Required),
2337                        ..Default::default()
2338                    }),
2339                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2340                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2341                        source_name: Some("fuchsia.logger.LogSink".into()),
2342                        target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
2343                            name: "coll".into(),
2344                        })),
2345                        target_name: Some("fuchsia.logger.LogSink".into()),
2346                        dependency_type: Some(fdecl::DependencyType::Strong),
2347                        availability: Some(fdecl::Availability::Required),
2348                        ..Default::default()
2349                    }),
2350                ]),
2351                children: Some(vec![fdecl::Child {
2352                    name: Some("logger".into()),
2353                    url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".into()),
2354                    startup: Some(fdecl::StartupMode::Lazy),
2355                    ..Default::default()
2356                }]),
2357                collections: Some(vec![fdecl::Collection {
2358                    name: Some("coll".into()),
2359                    durability: Some(fdecl::Durability::Transient),
2360                    ..Default::default()
2361                }]),
2362                ..default_component_decl()
2363            },
2364        },
2365
2366        test_compile_offer_to_all => {
2367            input = json!({
2368                "children": [
2369                    {
2370                        "name": "logger",
2371                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
2372                    },
2373                    {
2374                        "name": "something",
2375                        "url": "fuchsia-pkg://fuchsia.com/something/stable#meta/something.cm",
2376                    },
2377                ],
2378                "collections": [
2379                    {
2380                        "name": "coll",
2381                        "durability": "transient",
2382                    },
2383                ],
2384                "offer": [
2385                    {
2386                        "protocol": "fuchsia.logger.LogSink",
2387                        "from": "parent",
2388                        "to": "all",
2389                    },
2390                    {
2391                        "protocol": "fuchsia.inspect.InspectSink",
2392                        "from": "parent",
2393                        "to": "all",
2394                    },
2395                    {
2396                        "protocol": "fuchsia.logger.LegacyLog",
2397                        "from": "parent",
2398                        "to": "#logger",
2399                    },
2400                ],
2401            }),
2402            output = fdecl::Component {
2403                offers: Some(vec![
2404                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2405                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2406                        source_name: Some("fuchsia.logger.LegacyLog".into()),
2407                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2408                            name: "logger".into(),
2409                            collection: None,
2410                        })),
2411                        target_name: Some("fuchsia.logger.LegacyLog".into()),
2412                        dependency_type: Some(fdecl::DependencyType::Strong),
2413                        availability: Some(fdecl::Availability::Required),
2414                        ..Default::default()
2415                    }),
2416                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2417                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2418                        source_name: Some("fuchsia.logger.LogSink".into()),
2419                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2420                            name: "logger".into(),
2421                            collection: None,
2422                        })),
2423                        target_name: Some("fuchsia.logger.LogSink".into()),
2424                        dependency_type: Some(fdecl::DependencyType::Strong),
2425                        availability: Some(fdecl::Availability::Required),
2426                        ..Default::default()
2427                    }),
2428                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2429                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2430                        source_name: Some("fuchsia.logger.LogSink".into()),
2431                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2432                            name: "something".into(),
2433                            collection: None,
2434                        })),
2435                        target_name: Some("fuchsia.logger.LogSink".into()),
2436                        dependency_type: Some(fdecl::DependencyType::Strong),
2437                        availability: Some(fdecl::Availability::Required),
2438                        ..Default::default()
2439                    }),
2440                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2441                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2442                        source_name: Some("fuchsia.logger.LogSink".into()),
2443                        target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
2444                            name: "coll".into(),
2445                        })),
2446                        target_name: Some("fuchsia.logger.LogSink".into()),
2447                        dependency_type: Some(fdecl::DependencyType::Strong),
2448                        availability: Some(fdecl::Availability::Required),
2449                        ..Default::default()
2450                    }),
2451                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2452                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2453                        source_name: Some("fuchsia.inspect.InspectSink".into()),
2454                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2455                            name: "logger".into(),
2456                            collection: None,
2457                        })),
2458                        target_name: Some("fuchsia.inspect.InspectSink".into()),
2459                        dependency_type: Some(fdecl::DependencyType::Strong),
2460                        availability: Some(fdecl::Availability::Required),
2461                        ..Default::default()
2462                    }),
2463                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2464                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2465                        source_name: Some("fuchsia.inspect.InspectSink".into()),
2466                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2467                            name: "something".into(),
2468                            collection: None,
2469                        })),
2470                        target_name: Some("fuchsia.inspect.InspectSink".into()),
2471                        dependency_type: Some(fdecl::DependencyType::Strong),
2472                        availability: Some(fdecl::Availability::Required),
2473                        ..Default::default()
2474                    }),
2475                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2476                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2477                        source_name: Some("fuchsia.inspect.InspectSink".into()),
2478                        target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
2479                            name: "coll".into(),
2480                        })),
2481                        target_name: Some("fuchsia.inspect.InspectSink".into()),
2482                        dependency_type: Some(fdecl::DependencyType::Strong),
2483                        availability: Some(fdecl::Availability::Required),
2484                        ..Default::default()
2485                    }),
2486                ]),
2487                children: Some(vec![
2488                    fdecl::Child {
2489                        name: Some("logger".into()),
2490                        url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".into()),
2491                        startup: Some(fdecl::StartupMode::Lazy),
2492                        ..Default::default()
2493                    },
2494                    fdecl::Child {
2495                        name: Some("something".into()),
2496                        url: Some(
2497                            "fuchsia-pkg://fuchsia.com/something/stable#meta/something.cm".into(),
2498                        ),
2499                        startup: Some(fdecl::StartupMode::Lazy),
2500                        ..Default::default()
2501                    },
2502                ]),
2503                collections: Some(vec![fdecl::Collection {
2504                    name: Some("coll".into()),
2505                    durability: Some(fdecl::Durability::Transient),
2506                    ..Default::default()
2507                }]),
2508                ..default_component_decl()
2509            },
2510        },
2511
2512        test_compile_offer_to_all_hides_individual_duplicate_routes => {
2513            input = json!({
2514                "children": [
2515                    {
2516                        "name": "logger",
2517                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
2518                    },
2519                    {
2520                        "name": "something",
2521                        "url": "fuchsia-pkg://fuchsia.com/something/stable#meta/something.cm",
2522                    },
2523                    {
2524                        "name": "something-v2",
2525                        "url": "fuchsia-pkg://fuchsia.com/something/stable#meta/something-v2.cm",
2526                    },
2527                ],
2528                "collections": [
2529                    {
2530                        "name": "coll",
2531                        "durability": "transient",
2532                    },
2533                    {
2534                        "name": "coll2",
2535                        "durability": "transient",
2536                    },
2537                ],
2538                "offer": [
2539                    {
2540                        "protocol": "fuchsia.logger.LogSink",
2541                        "from": "parent",
2542                        "to": "#logger",
2543                    },
2544                    {
2545                        "protocol": "fuchsia.logger.LogSink",
2546                        "from": "parent",
2547                        "to": "all",
2548                    },
2549                    {
2550                        "protocol": "fuchsia.logger.LogSink",
2551                        "from": "parent",
2552                        "to": [ "#something", "#something-v2", "#coll2"],
2553                    },
2554                    {
2555                        "protocol": "fuchsia.logger.LogSink",
2556                        "from": "parent",
2557                        "to": "#coll",
2558                    },
2559                ],
2560            }),
2561            output = fdecl::Component {
2562                offers: Some(vec![
2563                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2564                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2565                        source_name: Some("fuchsia.logger.LogSink".into()),
2566                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2567                            name: "logger".into(),
2568                            collection: None,
2569                        })),
2570                        target_name: Some("fuchsia.logger.LogSink".into()),
2571                        dependency_type: Some(fdecl::DependencyType::Strong),
2572                        availability: Some(fdecl::Availability::Required),
2573                        ..Default::default()
2574                    }),
2575                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2576                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2577                        source_name: Some("fuchsia.logger.LogSink".into()),
2578                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2579                            name: "something".into(),
2580                            collection: None,
2581                        })),
2582                        target_name: Some("fuchsia.logger.LogSink".into()),
2583                        dependency_type: Some(fdecl::DependencyType::Strong),
2584                        availability: Some(fdecl::Availability::Required),
2585                        ..Default::default()
2586                    }),
2587                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2588                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2589                        source_name: Some("fuchsia.logger.LogSink".into()),
2590                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2591                            name: "something-v2".into(),
2592                            collection: None,
2593                        })),
2594                        target_name: Some("fuchsia.logger.LogSink".into()),
2595                        dependency_type: Some(fdecl::DependencyType::Strong),
2596                        availability: Some(fdecl::Availability::Required),
2597                        ..Default::default()
2598                    }),
2599                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2600                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2601                        source_name: Some("fuchsia.logger.LogSink".into()),
2602                        target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
2603                            name: "coll2".into(),
2604                        })),
2605                        target_name: Some("fuchsia.logger.LogSink".into()),
2606                        dependency_type: Some(fdecl::DependencyType::Strong),
2607                        availability: Some(fdecl::Availability::Required),
2608                        ..Default::default()
2609                    }),
2610                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2611                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2612                        source_name: Some("fuchsia.logger.LogSink".into()),
2613                        target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
2614                            name: "coll".into(),
2615                        })),
2616                        target_name: Some("fuchsia.logger.LogSink".into()),
2617                        dependency_type: Some(fdecl::DependencyType::Strong),
2618                        availability: Some(fdecl::Availability::Required),
2619                        ..Default::default()
2620                    }),
2621                ]),
2622                children: Some(vec![
2623                    fdecl::Child {
2624                        name: Some("logger".into()),
2625                        url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".into()),
2626                        startup: Some(fdecl::StartupMode::Lazy),
2627                        ..Default::default()
2628                    },
2629                    fdecl::Child {
2630                        name: Some("something".into()),
2631                        url: Some(
2632                            "fuchsia-pkg://fuchsia.com/something/stable#meta/something.cm".into(),
2633                        ),
2634                        startup: Some(fdecl::StartupMode::Lazy),
2635                        ..Default::default()
2636                    },
2637                    fdecl::Child {
2638                        name: Some("something-v2".into()),
2639                        url: Some(
2640                            "fuchsia-pkg://fuchsia.com/something/stable#meta/something-v2.cm".into(),
2641                        ),
2642                        startup: Some(fdecl::StartupMode::Lazy),
2643                        ..Default::default()
2644                    },
2645                ]),
2646                collections: Some(vec![fdecl::Collection {
2647                    name: Some("coll".into()),
2648                    durability: Some(fdecl::Durability::Transient),
2649                    ..Default::default()
2650                }, fdecl::Collection {
2651                    name: Some("coll2".into()),
2652                    durability: Some(fdecl::Durability::Transient),
2653                    ..Default::default()
2654                }]),
2655                ..default_component_decl()
2656            },
2657        },
2658
2659        test_compile_offer_to_all_from_child => {
2660            input = json!({
2661                "children": [
2662                    {
2663                        "name": "logger",
2664                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
2665                    },
2666                    {
2667                        "name": "something",
2668                        "url": "fuchsia-pkg://fuchsia.com/something/stable#meta/something.cm",
2669                    },
2670                    {
2671                        "name": "something-v2",
2672                        "url": "fuchsia-pkg://fuchsia.com/something/stable#meta/something-v2.cm",
2673                    },
2674                ],
2675                "offer": [
2676                    {
2677                        "protocol": "fuchsia.logger.LogSink",
2678                        "from": "#logger",
2679                        "to": "all",
2680                    },
2681                ],
2682            }),
2683            output = fdecl::Component {
2684                offers: Some(vec![
2685                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2686                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
2687                            name: "logger".into(),
2688                            collection: None,
2689                        })),
2690                        source_name: Some("fuchsia.logger.LogSink".into()),
2691                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2692                            name: "something".into(),
2693                            collection: None,
2694                        })),
2695                        target_name: Some("fuchsia.logger.LogSink".into()),
2696                        dependency_type: Some(fdecl::DependencyType::Strong),
2697                        availability: Some(fdecl::Availability::Required),
2698                        ..Default::default()
2699                    }),
2700                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2701                        source: Some(fdecl::Ref::Child(fdecl::ChildRef {
2702                            name: "logger".into(),
2703                            collection: None,
2704                        })),
2705                        source_name: Some("fuchsia.logger.LogSink".into()),
2706                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2707                            name: "something-v2".into(),
2708                            collection: None,
2709                        })),
2710                        target_name: Some("fuchsia.logger.LogSink".into()),
2711                        dependency_type: Some(fdecl::DependencyType::Strong),
2712                        availability: Some(fdecl::Availability::Required),
2713                        ..Default::default()
2714                    }),
2715                ]),
2716                children: Some(vec![
2717                    fdecl::Child {
2718                        name: Some("logger".into()),
2719                        url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".into()),
2720                        startup: Some(fdecl::StartupMode::Lazy),
2721                        ..Default::default()
2722                    },
2723                    fdecl::Child {
2724                        name: Some("something".into()),
2725                        url: Some(
2726                            "fuchsia-pkg://fuchsia.com/something/stable#meta/something.cm".into(),
2727                        ),
2728                        startup: Some(fdecl::StartupMode::Lazy),
2729                        ..Default::default()
2730                    },
2731                    fdecl::Child {
2732                        name: Some("something-v2".into()),
2733                        url: Some(
2734                            "fuchsia-pkg://fuchsia.com/something/stable#meta/something-v2.cm".into(),
2735                        ),
2736                        startup: Some(fdecl::StartupMode::Lazy),
2737                        ..Default::default()
2738                    },
2739                ]),
2740                ..default_component_decl()
2741            },
2742        },
2743
2744        test_compile_offer_multiple_protocols_to_single_array_syntax_and_all => {
2745            input = json!({
2746                "children": [
2747                    {
2748                        "name": "something",
2749                        "url": "fuchsia-pkg://fuchsia.com/something/stable#meta/something.cm",
2750                    },
2751                ],
2752                "offer": [
2753                    {
2754                        "protocol": ["fuchsia.logger.LogSink", "fuchsia.inspect.InspectSink",],
2755                        "from": "parent",
2756                        "to": "#something",
2757                    },
2758                    {
2759                        "protocol": "fuchsia.logger.LogSink",
2760                        "from": "parent",
2761                        "to": "all",
2762                    },
2763                ],
2764            }),
2765            output = fdecl::Component {
2766                offers: Some(vec![
2767                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2768                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2769                        source_name: Some("fuchsia.logger.LogSink".into()),
2770                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2771                            name: "something".into(),
2772                            collection: None,
2773                        })),
2774                        target_name: Some("fuchsia.logger.LogSink".into()),
2775                        dependency_type: Some(fdecl::DependencyType::Strong),
2776                        availability: Some(fdecl::Availability::Required),
2777                        ..Default::default()
2778                    }),
2779                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2780                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2781                        source_name: Some("fuchsia.inspect.InspectSink".into()),
2782                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2783                            name: "something".into(),
2784                            collection: None,
2785                        })),
2786                        target_name: Some("fuchsia.inspect.InspectSink".into()),
2787                        dependency_type: Some(fdecl::DependencyType::Strong),
2788                        availability: Some(fdecl::Availability::Required),
2789                        ..Default::default()
2790                    }),
2791                ]),
2792                children: Some(vec![
2793                    fdecl::Child {
2794                        name: Some("something".into()),
2795                        url: Some(
2796                            "fuchsia-pkg://fuchsia.com/something/stable#meta/something.cm".into(),
2797                        ),
2798                        startup: Some(fdecl::StartupMode::Lazy),
2799                        ..Default::default()
2800                    },
2801                ]),
2802                ..default_component_decl()
2803            },
2804        },
2805
2806        test_compile_offer_to_all_array_and_single => {
2807            input = json!({
2808                "children": [
2809                    {
2810                        "name": "something",
2811                        "url": "fuchsia-pkg://fuchsia.com/something/stable#meta/something.cm",
2812                    },
2813                ],
2814                "offer": [
2815                    {
2816                        "protocol": ["fuchsia.logger.LogSink", "fuchsia.inspect.InspectSink",],
2817                        "from": "parent",
2818                        "to": "all",
2819                    },
2820                    {
2821                        "protocol": "fuchsia.logger.LogSink",
2822                        "from": "parent",
2823                        "to": "#something",
2824                    },
2825                ],
2826            }),
2827            output = fdecl::Component {
2828                offers: Some(vec![
2829                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2830                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2831                        source_name: Some("fuchsia.logger.LogSink".into()),
2832                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2833                            name: "something".into(),
2834                            collection: None,
2835                        })),
2836                        target_name: Some("fuchsia.logger.LogSink".into()),
2837                        dependency_type: Some(fdecl::DependencyType::Strong),
2838                        availability: Some(fdecl::Availability::Required),
2839                        ..Default::default()
2840                    }),
2841                    fdecl::Offer::Protocol(fdecl::OfferProtocol {
2842                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2843                        source_name: Some("fuchsia.inspect.InspectSink".into()),
2844                        target: Some(fdecl::Ref::Child(fdecl::ChildRef {
2845                            name: "something".into(),
2846                            collection: None,
2847                        })),
2848                        target_name: Some("fuchsia.inspect.InspectSink".into()),
2849                        dependency_type: Some(fdecl::DependencyType::Strong),
2850                        availability: Some(fdecl::Availability::Required),
2851                        ..Default::default()
2852                    }),
2853                ]),
2854                children: Some(vec![
2855                    fdecl::Child {
2856                        name: Some("something".into()),
2857                        url: Some(
2858                            "fuchsia-pkg://fuchsia.com/something/stable#meta/something.cm".into(),
2859                        ),
2860                        startup: Some(fdecl::StartupMode::Lazy),
2861                        ..Default::default()
2862                    },
2863                ]),
2864                ..default_component_decl()
2865            },
2866        },
2867
2868        test_compile_program => {
2869            input = json!({
2870                "program": {
2871                    "runner": "elf",
2872                    "binary": "bin/app",
2873                },
2874            }),
2875            output = fdecl::Component {
2876                program: Some(fdecl::Program {
2877                    runner: Some("elf".to_string()),
2878                    info: Some(fdata::Dictionary {
2879                        entries: Some(vec![fdata::DictionaryEntry {
2880                            key: "binary".to_string(),
2881                            value: Some(Box::new(fdata::DictionaryValue::Str("bin/app".to_string()))),
2882                        }]),
2883                        ..Default::default()
2884                    }),
2885                    ..Default::default()
2886                }),
2887                ..default_component_decl()
2888            },
2889        },
2890
2891        test_compile_program_with_use_runner => {
2892            input = json!({
2893                "program": {
2894                    "binary": "bin/app",
2895                },
2896                "use": [
2897                    { "runner": "elf", "from": "parent", },
2898                ],
2899            }),
2900            output = fdecl::Component {
2901                program: Some(fdecl::Program {
2902                    runner: None,
2903                    info: Some(fdata::Dictionary {
2904                        entries: Some(vec![fdata::DictionaryEntry {
2905                            key: "binary".to_string(),
2906                            value: Some(Box::new(fdata::DictionaryValue::Str("bin/app".to_string()))),
2907                        }]),
2908                        ..Default::default()
2909                    }),
2910                    ..Default::default()
2911                }),
2912                uses: Some(vec![
2913                    fdecl::Use::Runner (
2914                        fdecl::UseRunner {
2915                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
2916                            source_name: Some("elf".to_string()),
2917                            ..Default::default()
2918                        }
2919                    ),
2920                ]),
2921                ..default_component_decl()
2922            },
2923        },
2924
2925        test_compile_program_with_nested_objects => {
2926            input = json!({
2927                "program": {
2928                    "runner": "elf",
2929                    "binary": "bin/app",
2930                    "one": {
2931                        "two": {
2932                            "three.four": {
2933                                "five": "six"
2934                            }
2935                        },
2936                    }
2937                },
2938            }),
2939            output = fdecl::Component {
2940                program: Some(fdecl::Program {
2941                    runner: Some("elf".to_string()),
2942                    info: Some(fdata::Dictionary {
2943                        entries: Some(vec![
2944                            fdata::DictionaryEntry {
2945                                key: "binary".to_string(),
2946                                value: Some(Box::new(fdata::DictionaryValue::Str("bin/app".to_string()))),
2947                            },
2948                            fdata::DictionaryEntry {
2949                                key: "one.two.three.four.five".to_string(),
2950                                value: Some(Box::new(fdata::DictionaryValue::Str("six".to_string()))),
2951                            },
2952                        ]),
2953                        ..Default::default()
2954                    }),
2955                    ..Default::default()
2956                }),
2957                ..default_component_decl()
2958            },
2959        },
2960
2961        test_compile_program_with_array_of_objects => {
2962            input = json!({
2963                "program": {
2964                    "runner": "elf",
2965                    "binary": "bin/app",
2966                    "networks": [
2967                        {
2968                            "endpoints": [
2969                                {
2970                                    "name": "device",
2971                                    "mac": "aa:bb:cc:dd:ee:ff"
2972                                },
2973                                {
2974                                    "name": "emu",
2975                                    "mac": "ff:ee:dd:cc:bb:aa"
2976                                },
2977                            ],
2978                            "name": "external_network"
2979                        }
2980                    ],
2981                },
2982            }),
2983            output = fdecl::Component {
2984                program: Some(fdecl::Program {
2985                    runner: Some("elf".to_string()),
2986                    info: Some(fdata::Dictionary {
2987                        entries: Some(vec![
2988                            fdata::DictionaryEntry {
2989                                key: "binary".to_string(),
2990                                value: Some(Box::new(fdata::DictionaryValue::Str("bin/app".to_string()))),
2991                            },
2992                            fdata::DictionaryEntry {
2993                                key: "networks".to_string(),
2994                                value: Some(Box::new(fdata::DictionaryValue::ObjVec(vec![
2995                                    fdata::Dictionary {
2996                                        entries: Some(vec![
2997                                            fdata::DictionaryEntry {
2998                                                key: "endpoints".to_string(),
2999                                                value: Some(Box::new(fdata::DictionaryValue::ObjVec(vec![
3000                                                    fdata::Dictionary {
3001                                                        entries: Some(vec![
3002                                                            fdata::DictionaryEntry {
3003                                                                key: "mac".to_string(),
3004                                                                value: Some(Box::new(fdata::DictionaryValue::Str("aa:bb:cc:dd:ee:ff".to_string()))),
3005                                                            },
3006                                                            fdata::DictionaryEntry {
3007                                                                key: "name".to_string(),
3008                                                                value: Some(Box::new(fdata::DictionaryValue::Str("device".to_string()))),
3009                                                            }
3010                                                        ]),
3011                                                        ..Default::default()
3012                                                    },
3013                                                    fdata::Dictionary {
3014                                                        entries: Some(vec![
3015                                                            fdata::DictionaryEntry {
3016                                                                key: "mac".to_string(),
3017                                                                value: Some(Box::new(fdata::DictionaryValue::Str("ff:ee:dd:cc:bb:aa".to_string()))),
3018                                                            },
3019                                                            fdata::DictionaryEntry {
3020                                                                key: "name".to_string(),
3021                                                                value: Some(Box::new(fdata::DictionaryValue::Str("emu".to_string()))),
3022                                                            }
3023                                                        ]),
3024                                                        ..Default::default()
3025                                                    },
3026                                                ])))
3027                                            },
3028                                            fdata::DictionaryEntry {
3029                                                key: "name".to_string(),
3030                                                value: Some(Box::new(fdata::DictionaryValue::Str("external_network".to_string()))),
3031                                            },
3032                                        ]),
3033                                        ..Default::default()
3034                                    }
3035                                ]))),
3036                            },
3037                        ]),
3038                        ..Default::default()
3039                    }),
3040                    ..Default::default()
3041                }),
3042                ..default_component_decl()
3043            },
3044        },
3045
3046        test_compile_use => {
3047            input = json!({
3048                "use": [
3049                    {
3050                        "protocol": "LegacyCoolFonts",
3051                        "path": "/svc/fuchsia.fonts.LegacyProvider",
3052                        "availability": "optional",
3053                    },
3054                    { "protocol": "fuchsia.sys2.LegacyRealm", "from": "framework" },
3055                    { "protocol": "fuchsia.sys2.StorageAdmin", "from": "#data-storage" },
3056                    { "protocol": "fuchsia.sys2.DebugProto", "from": "debug" },
3057                    { "protocol": "fuchsia.sys2.DictionaryProto", "from": "#logger/in/dict" },
3058                    { "protocol": "fuchsia.sys2.Echo", "from": "self", "availability": "transitional" },
3059                    { "service": "fuchsia.sys2.EchoService", "from": "parent/dict", },
3060                    { "directory": "assets", "rights" : ["read_bytes"], "path": "/data/assets" },
3061                    {
3062                        "directory": "config",
3063                        "path": "/data/config",
3064                        "from": "parent",
3065                        "rights": ["read_bytes"],
3066                        "subdir": "fonts",
3067                    },
3068                    { "storage": "hippos", "path": "/hippos" },
3069                    { "storage": "cache", "path": "/tmp" },
3070                    {
3071                        "event_stream": "bar_stream",
3072                    },
3073                    {
3074                        "event_stream": ["foobar", "stream"],
3075                        "scope": ["#logger", "#modular"],
3076                        "path": "/event_stream/another",
3077                    },
3078                    { "runner": "usain", "from": "parent", },
3079                ],
3080                "capabilities": [
3081                    { "protocol": "fuchsia.sys2.Echo" },
3082                    {
3083                        "config": "fuchsia.config.Config",
3084                        "type": "bool",
3085                        "value": true,
3086                    },
3087                    {
3088                        "storage": "data-storage",
3089                        "from": "parent",
3090                        "backing_dir": "minfs",
3091                        "storage_id": "static_instance_id_or_moniker",
3092                    }
3093                ],
3094                "children": [
3095                    {
3096                        "name": "logger",
3097                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
3098                        "environment": "#env_one"
3099                    }
3100                ],
3101                "collections": [
3102                    {
3103                        "name": "modular",
3104                        "durability": "transient",
3105                    },
3106                ],
3107                "environments": [
3108                    {
3109                        "name": "env_one",
3110                        "extends": "realm",
3111                    }
3112                ]
3113            }),
3114            output = fdecl::Component {
3115                uses: Some(vec![
3116                    fdecl::Use::Protocol (
3117                        fdecl::UseProtocol {
3118                            dependency_type: Some(fdecl::DependencyType::Strong),
3119                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3120                            source_name: Some("LegacyCoolFonts".to_string()),
3121                            target_path: Some("/svc/fuchsia.fonts.LegacyProvider".to_string()),
3122                            availability: Some(fdecl::Availability::Optional),
3123                            ..Default::default()
3124                        }
3125                    ),
3126                    fdecl::Use::Protocol (
3127                        fdecl::UseProtocol {
3128                            dependency_type: Some(fdecl::DependencyType::Strong),
3129                            source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef {})),
3130                            source_name: Some("fuchsia.sys2.LegacyRealm".to_string()),
3131                            target_path: Some("/svc/fuchsia.sys2.LegacyRealm".to_string()),
3132                            availability: Some(fdecl::Availability::Required),
3133                            ..Default::default()
3134                        }
3135                    ),
3136                    fdecl::Use::Protocol (
3137                        fdecl::UseProtocol {
3138                            dependency_type: Some(fdecl::DependencyType::Strong),
3139                            source: Some(fdecl::Ref::Capability(fdecl::CapabilityRef { name: "data-storage".to_string() })),
3140                            source_name: Some("fuchsia.sys2.StorageAdmin".to_string()),
3141                            target_path: Some("/svc/fuchsia.sys2.StorageAdmin".to_string()),
3142                            availability: Some(fdecl::Availability::Required),
3143                            ..Default::default()
3144                        }
3145                    ),
3146                    fdecl::Use::Protocol (
3147                        fdecl::UseProtocol {
3148                            dependency_type: Some(fdecl::DependencyType::Strong),
3149                            source: Some(fdecl::Ref::Debug(fdecl::DebugRef {})),
3150                            source_name: Some("fuchsia.sys2.DebugProto".to_string()),
3151                            target_path: Some("/svc/fuchsia.sys2.DebugProto".to_string()),
3152                            availability: Some(fdecl::Availability::Required),
3153                            ..Default::default()
3154                        }
3155                    ),
3156                    fdecl::Use::Protocol (
3157                        fdecl::UseProtocol {
3158                            dependency_type: Some(fdecl::DependencyType::Strong),
3159                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3160                                name: "logger".into(),
3161                                collection: None,
3162                            })),
3163                            #[cfg(fuchsia_api_level_at_least = "25")]
3164                            source_dictionary: Some("in/dict".into()),
3165                            source_name: Some("fuchsia.sys2.DictionaryProto".to_string()),
3166                            target_path: Some("/svc/fuchsia.sys2.DictionaryProto".to_string()),
3167                            availability: Some(fdecl::Availability::Required),
3168                            ..Default::default()
3169                        }
3170                    ),
3171                    fdecl::Use::Protocol (
3172                        fdecl::UseProtocol {
3173                            dependency_type: Some(fdecl::DependencyType::Strong),
3174                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
3175                            source_name: Some("fuchsia.sys2.Echo".to_string()),
3176                            target_path: Some("/svc/fuchsia.sys2.Echo".to_string()),
3177                            availability: Some(fdecl::Availability::Transitional),
3178                            ..Default::default()
3179                        }
3180                    ),
3181                    fdecl::Use::Service (
3182                        fdecl::UseService {
3183                            dependency_type: Some(fdecl::DependencyType::Strong),
3184                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3185                            #[cfg(fuchsia_api_level_at_least = "25")]
3186                            source_dictionary: Some("dict".into()),
3187                            source_name: Some("fuchsia.sys2.EchoService".to_string()),
3188                            target_path: Some("/svc/fuchsia.sys2.EchoService".to_string()),
3189                            availability: Some(fdecl::Availability::Required),
3190                            ..Default::default()
3191                        }
3192                    ),
3193                    fdecl::Use::Directory (
3194                        fdecl::UseDirectory {
3195                            dependency_type: Some(fdecl::DependencyType::Strong),
3196                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3197                            source_name: Some("assets".to_string()),
3198                            target_path: Some("/data/assets".to_string()),
3199                            rights: Some(fio::Operations::READ_BYTES),
3200                            subdir: None,
3201                            availability: Some(fdecl::Availability::Required),
3202                            ..Default::default()
3203                        }
3204                    ),
3205                    fdecl::Use::Directory (
3206                        fdecl::UseDirectory {
3207                            dependency_type: Some(fdecl::DependencyType::Strong),
3208                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3209                            source_name: Some("config".to_string()),
3210                            target_path: Some("/data/config".to_string()),
3211                            rights: Some(fio::Operations::READ_BYTES),
3212                            subdir: Some("fonts".to_string()),
3213                            availability: Some(fdecl::Availability::Required),
3214                            ..Default::default()
3215                        }
3216                    ),
3217                    fdecl::Use::Storage (
3218                        fdecl::UseStorage {
3219                            source_name: Some("hippos".to_string()),
3220                            target_path: Some("/hippos".to_string()),
3221                            availability: Some(fdecl::Availability::Required),
3222                            ..Default::default()
3223                        }
3224                    ),
3225                    fdecl::Use::Storage (
3226                        fdecl::UseStorage {
3227                            source_name: Some("cache".to_string()),
3228                            target_path: Some("/tmp".to_string()),
3229                            availability: Some(fdecl::Availability::Required),
3230                            ..Default::default()
3231                        }
3232                    ),
3233                    fdecl::Use::EventStream(fdecl::UseEventStream {
3234                        source_name: Some("bar_stream".to_string()),
3235                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
3236                        target_path: Some("/svc/fuchsia.component.EventStream".to_string()),
3237                        availability: Some(fdecl::Availability::Required),
3238                        ..Default::default()
3239                    }),
3240                    fdecl::Use::EventStream(fdecl::UseEventStream {
3241                        source_name: Some("foobar".to_string()),
3242                        scope: Some(vec![fdecl::Ref::Child(fdecl::ChildRef{name:"logger".to_string(), collection: None}), fdecl::Ref::Collection(fdecl::CollectionRef{name:"modular".to_string()})]),
3243                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
3244                        target_path: Some("/event_stream/another".to_string()),
3245                        availability: Some(fdecl::Availability::Required),
3246                        ..Default::default()
3247                    }),
3248                    fdecl::Use::EventStream(fdecl::UseEventStream {
3249                        source_name: Some("stream".to_string()),
3250                        scope: Some(vec![fdecl::Ref::Child(fdecl::ChildRef{name:"logger".to_string(), collection: None}), fdecl::Ref::Collection(fdecl::CollectionRef{name:"modular".to_string()})]),
3251                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
3252                        target_path: Some("/event_stream/another".to_string()),
3253                        availability: Some(fdecl::Availability::Required),
3254                        ..Default::default()
3255                    }),
3256                    fdecl::Use::Runner(fdecl::UseRunner {
3257                        source_name: Some("usain".to_string()),
3258                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
3259                        ..Default::default()
3260                    }),
3261                ]),
3262                collections: Some(vec![
3263                    fdecl::Collection{
3264                        name:Some("modular".to_string()),
3265                        durability:Some(fdecl::Durability::Transient),
3266                        ..Default::default()
3267                    },
3268                ]),
3269                capabilities: Some(vec![
3270                    fdecl::Capability::Protocol(fdecl::Protocol {
3271                        name: Some("fuchsia.sys2.Echo".to_string()),
3272                        source_path: Some("/svc/fuchsia.sys2.Echo".to_string()),
3273                        ..Default::default()
3274                    }),
3275                    fdecl::Capability::Config(fdecl::Configuration {
3276                        name: Some("fuchsia.config.Config".to_string()),
3277                        value: Some(fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Bool(true))),
3278                        ..Default::default()
3279                    }),
3280                    fdecl::Capability::Storage(fdecl::Storage {
3281                        name: Some("data-storage".to_string()),
3282                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3283                        backing_dir: Some("minfs".to_string()),
3284                        subdir: None,
3285                        storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker),
3286                        ..Default::default()
3287                    }),
3288                ]),
3289                children: Some(vec![
3290                    fdecl::Child{
3291                        name:Some("logger".to_string()),
3292                        url:Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
3293                        startup:Some(fdecl::StartupMode::Lazy),
3294                        environment: Some("env_one".to_string()),
3295                        ..Default::default()
3296                    }
3297                ]),
3298                environments: Some(vec![
3299                    fdecl::Environment {
3300                        name: Some("env_one".to_string()),
3301                        extends: Some(fdecl::EnvironmentExtends::Realm),
3302                        ..Default::default()
3303                    },
3304                ]),
3305                config: None,
3306                ..default_component_decl()
3307            },
3308        },
3309
3310        test_compile_expose => {
3311            input = json!({
3312                "expose": [
3313                    {
3314                        "protocol": "fuchsia.logger.Log",
3315                        "from": "#logger",
3316                        "as": "fuchsia.logger.LegacyLog",
3317                        "to": "parent"
3318                    },
3319                    {
3320                        "protocol": [ "A", "B" ],
3321                        "from": "self",
3322                        "to": "parent"
3323                    },
3324                    {
3325                        "protocol": "C",
3326                        "from": "#data-storage",
3327                    },
3328                    {
3329                        "protocol": "D",
3330                        "from": "#logger/in/dict",
3331                        "as": "E",
3332                    },
3333                    {
3334                        "service": "F",
3335                        "from": "#logger/in/dict",
3336                    },
3337                    {
3338                        "service": "svc",
3339                        "from": [ "#logger", "#coll", "self" ],
3340                    },
3341                    {
3342                        "directory": "blob",
3343                        "from": "self",
3344                        "to": "framework",
3345                        "rights": ["r*"],
3346                    },
3347                    {
3348                        "directory": [ "blob2", "blob3" ],
3349                        "from": "#logger",
3350                        "to": "parent",
3351                    },
3352                    { "directory": "hub", "from": "framework" },
3353                    { "runner": "web", "from": "#logger", "to": "parent", "as": "web-rename" },
3354                    { "runner": [ "runner_a", "runner_b" ], "from": "#logger" },
3355                    { "resolver": "my_resolver", "from": "#logger", "to": "parent", "as": "pkg_resolver" },
3356                    { "resolver": [ "resolver_a", "resolver_b" ], "from": "#logger" },
3357                    { "dictionary": [ "dictionary_a", "dictionary_b" ], "from": "#logger" },
3358                ],
3359                "capabilities": [
3360                    { "protocol": "A" },
3361                    { "protocol": "B" },
3362                    { "service": "svc" },
3363                    {
3364                        "directory": "blob",
3365                        "path": "/volumes/blobfs/blob",
3366                        "rights": ["r*"],
3367                    },
3368                    {
3369                        "runner": "web",
3370                        "path": "/svc/fuchsia.component.ComponentRunner",
3371                    },
3372                    {
3373                        "storage": "data-storage",
3374                        "from": "parent",
3375                        "backing_dir": "minfs",
3376                        "storage_id": "static_instance_id_or_moniker",
3377                    },
3378                ],
3379                "children": [
3380                    {
3381                        "name": "logger",
3382                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
3383                    },
3384                ],
3385                "collections": [
3386                    {
3387                        "name": "coll",
3388                        "durability": "transient",
3389                    },
3390                ],
3391            }),
3392            output = fdecl::Component {
3393                exposes: Some(vec![
3394                    fdecl::Expose::Protocol (
3395                        fdecl::ExposeProtocol {
3396                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3397                                name: "logger".to_string(),
3398                                collection: None,
3399                            })),
3400                            source_name: Some("fuchsia.logger.Log".to_string()),
3401                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3402                            target_name: Some("fuchsia.logger.LegacyLog".to_string()),
3403                            availability: Some(fdecl::Availability::Required),
3404                            ..Default::default()
3405                        }
3406                    ),
3407                    fdecl::Expose::Protocol (
3408                        fdecl::ExposeProtocol {
3409                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
3410                            source_name: Some("A".to_string()),
3411                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3412                            target_name: Some("A".to_string()),
3413                            availability: Some(fdecl::Availability::Required),
3414                            ..Default::default()
3415                        }
3416                    ),
3417                    fdecl::Expose::Protocol (
3418                        fdecl::ExposeProtocol {
3419                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
3420                            source_name: Some("B".to_string()),
3421                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3422                            target_name: Some("B".to_string()),
3423                            availability: Some(fdecl::Availability::Required),
3424                            ..Default::default()
3425                        }
3426                    ),
3427                    fdecl::Expose::Protocol (
3428                        fdecl::ExposeProtocol {
3429                            source: Some(fdecl::Ref::Capability(fdecl::CapabilityRef {
3430                                name: "data-storage".to_string(),
3431                            })),
3432                            source_name: Some("C".to_string()),
3433                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3434                            target_name: Some("C".to_string()),
3435                            availability: Some(fdecl::Availability::Required),
3436                            ..Default::default()
3437                        }
3438                    ),
3439                    fdecl::Expose::Protocol (
3440                        fdecl::ExposeProtocol {
3441                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3442                                name: "logger".to_string(),
3443                                collection: None,
3444                            })),
3445                            #[cfg(fuchsia_api_level_at_least = "25")]
3446                            source_dictionary: Some("in/dict".into()),
3447                            source_name: Some("D".to_string()),
3448                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3449                            target_name: Some("E".to_string()),
3450                            availability: Some(fdecl::Availability::Required),
3451                            ..Default::default()
3452                        }
3453                    ),
3454                    fdecl::Expose::Service (
3455                        fdecl::ExposeService {
3456                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3457                                name: "logger".into(),
3458                                collection: None,
3459                            })),
3460                            source_name: Some("F".into()),
3461                            #[cfg(fuchsia_api_level_at_least = "25")]
3462                            source_dictionary: Some("in/dict".into()),
3463                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3464                            target_name: Some("F".into()),
3465                            availability: Some(fdecl::Availability::Required),
3466                            ..Default::default()
3467                        }
3468                    ),
3469                    fdecl::Expose::Service (
3470                        fdecl::ExposeService {
3471                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3472                                name: "logger".into(),
3473                                collection: None,
3474                            })),
3475                            source_name: Some("svc".into()),
3476                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3477                            target_name: Some("svc".into()),
3478                            availability: Some(fdecl::Availability::Required),
3479                            ..Default::default()
3480                        }
3481                    ),
3482                    fdecl::Expose::Service (
3483                        fdecl::ExposeService {
3484                            source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
3485                                name: "coll".into(),
3486                            })),
3487                            source_name: Some("svc".into()),
3488                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3489                            target_name: Some("svc".into()),
3490                            availability: Some(fdecl::Availability::Required),
3491                            ..Default::default()
3492                        }
3493                    ),
3494                    fdecl::Expose::Service (
3495                        fdecl::ExposeService {
3496                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
3497                            source_name: Some("svc".into()),
3498                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3499                            target_name: Some("svc".into()),
3500                            availability: Some(fdecl::Availability::Required),
3501                            ..Default::default()
3502                        }
3503                    ),
3504                    fdecl::Expose::Directory (
3505                        fdecl::ExposeDirectory {
3506                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
3507                            source_name: Some("blob".to_string()),
3508                            target: Some(fdecl::Ref::Framework(fdecl::FrameworkRef {})),
3509                            target_name: Some("blob".to_string()),
3510                            rights: Some(
3511                                fio::Operations::CONNECT | fio::Operations::ENUMERATE |
3512                                fio::Operations::TRAVERSE | fio::Operations::READ_BYTES |
3513                                fio::Operations::GET_ATTRIBUTES
3514                            ),
3515                            subdir: None,
3516                            availability: Some(fdecl::Availability::Required),
3517                            ..Default::default()
3518                        }
3519                    ),
3520                    fdecl::Expose::Directory (
3521                        fdecl::ExposeDirectory {
3522                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3523                                name: "logger".to_string(),
3524                                collection: None,
3525                            })),
3526                            source_name: Some("blob2".to_string()),
3527                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3528                            target_name: Some("blob2".to_string()),
3529                            rights: None,
3530                            subdir: None,
3531                            availability: Some(fdecl::Availability::Required),
3532                            ..Default::default()
3533                        }
3534                    ),
3535                    fdecl::Expose::Directory (
3536                        fdecl::ExposeDirectory {
3537                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3538                                name: "logger".to_string(),
3539                                collection: None,
3540                            })),
3541                            source_name: Some("blob3".to_string()),
3542                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3543                            target_name: Some("blob3".to_string()),
3544                            rights: None,
3545                            subdir: None,
3546                            availability: Some(fdecl::Availability::Required),
3547                            ..Default::default()
3548                        }
3549                    ),
3550                    fdecl::Expose::Directory (
3551                        fdecl::ExposeDirectory {
3552                            source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef {})),
3553                            source_name: Some("hub".to_string()),
3554                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3555                            target_name: Some("hub".to_string()),
3556                            rights: None,
3557                            subdir: None,
3558                            availability: Some(fdecl::Availability::Required),
3559                            ..Default::default()
3560                        }
3561                    ),
3562                    fdecl::Expose::Runner (
3563                        fdecl::ExposeRunner {
3564                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3565                                name: "logger".to_string(),
3566                                collection: None,
3567                            })),
3568                            source_name: Some("web".to_string()),
3569                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3570                            target_name: Some("web-rename".to_string()),
3571                            ..Default::default()
3572                        }
3573                    ),
3574                    fdecl::Expose::Runner (
3575                        fdecl::ExposeRunner {
3576                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3577                                name: "logger".to_string(),
3578                                collection: None,
3579                            })),
3580                            source_name: Some("runner_a".to_string()),
3581                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3582                            target_name: Some("runner_a".to_string()),
3583                            ..Default::default()
3584                        }
3585                    ),
3586                    fdecl::Expose::Runner (
3587                        fdecl::ExposeRunner {
3588                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3589                                name: "logger".to_string(),
3590                                collection: None,
3591                            })),
3592                            source_name: Some("runner_b".to_string()),
3593                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3594                            target_name: Some("runner_b".to_string()),
3595                            ..Default::default()
3596                        }
3597                    ),
3598                    fdecl::Expose::Resolver (
3599                        fdecl::ExposeResolver {
3600                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3601                                name: "logger".to_string(),
3602                                collection: None,
3603                            })),
3604                            source_name: Some("my_resolver".to_string()),
3605                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3606                            target_name: Some("pkg_resolver".to_string()),
3607                            ..Default::default()
3608                        }
3609                    ),
3610                    fdecl::Expose::Resolver (
3611                        fdecl::ExposeResolver {
3612                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3613                                name: "logger".to_string(),
3614                                collection: None,
3615                            })),
3616                            source_name: Some("resolver_a".to_string()),
3617                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3618                            target_name: Some("resolver_a".to_string()),
3619                            ..Default::default()
3620                        }
3621                    ),
3622                    fdecl::Expose::Resolver (
3623                        fdecl::ExposeResolver {
3624                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3625                                name: "logger".to_string(),
3626                                collection: None,
3627                            })),
3628                            source_name: Some("resolver_b".to_string()),
3629                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3630                            target_name: Some("resolver_b".to_string()),
3631                            ..Default::default()
3632                        }
3633                    ),
3634                   fdecl::Expose::Dictionary (
3635                        fdecl::ExposeDictionary {
3636                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3637                                name: "logger".to_string(),
3638                                collection: None,
3639                            })),
3640                            source_name: Some("dictionary_a".to_string()),
3641                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3642                            target_name: Some("dictionary_a".to_string()),
3643                            availability: Some(fdecl::Availability::Required),
3644                            ..Default::default()
3645                        }
3646                    ),
3647                    fdecl::Expose::Dictionary (
3648                        fdecl::ExposeDictionary {
3649                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3650                                name: "logger".to_string(),
3651                                collection: None,
3652                            })),
3653                            source_name: Some("dictionary_b".to_string()),
3654                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3655                            target_name: Some("dictionary_b".to_string()),
3656                            availability: Some(fdecl::Availability::Required),
3657                            ..Default::default()
3658                        }
3659                    ),
3660                ]),
3661                offers: None,
3662                capabilities: Some(vec![
3663                    fdecl::Capability::Protocol (
3664                        fdecl::Protocol {
3665                            name: Some("A".to_string()),
3666                            source_path: Some("/svc/A".to_string()),
3667                            ..Default::default()
3668                        }
3669                    ),
3670                    fdecl::Capability::Protocol (
3671                        fdecl::Protocol {
3672                            name: Some("B".to_string()),
3673                            source_path: Some("/svc/B".to_string()),
3674                            ..Default::default()
3675                        }
3676                    ),
3677                    fdecl::Capability::Service (
3678                        fdecl::Service {
3679                            name: Some("svc".to_string()),
3680                            source_path: Some("/svc/svc".to_string()),
3681                            ..Default::default()
3682                        }
3683                    ),
3684                    fdecl::Capability::Directory (
3685                        fdecl::Directory {
3686                            name: Some("blob".to_string()),
3687                            source_path: Some("/volumes/blobfs/blob".to_string()),
3688                            rights: Some(fio::Operations::CONNECT | fio::Operations::ENUMERATE |
3689                                fio::Operations::TRAVERSE | fio::Operations::READ_BYTES |
3690                                fio::Operations::GET_ATTRIBUTES
3691                            ),
3692                            ..Default::default()
3693                        }
3694                    ),
3695                    fdecl::Capability::Runner (
3696                        fdecl::Runner {
3697                            name: Some("web".to_string()),
3698                            source_path: Some("/svc/fuchsia.component.ComponentRunner".to_string()),
3699                            ..Default::default()
3700                        }
3701                    ),
3702                    fdecl::Capability::Storage(fdecl::Storage {
3703                        name: Some("data-storage".to_string()),
3704                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3705                        backing_dir: Some("minfs".to_string()),
3706                        subdir: None,
3707                        storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker),
3708                        ..Default::default()
3709                    }),
3710                ]),
3711                children: Some(vec![
3712                    fdecl::Child {
3713                        name: Some("logger".to_string()),
3714                        url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
3715                        startup: Some(fdecl::StartupMode::Lazy),
3716                        ..Default::default()
3717                    }
3718                ]),
3719                collections: Some(vec![
3720                    fdecl::Collection {
3721                        name: Some("coll".to_string()),
3722                        durability: Some(fdecl::Durability::Transient),
3723                        ..Default::default()
3724                    }
3725                ]),
3726                ..default_component_decl()
3727            },
3728        },
3729
3730        test_compile_expose_other_availability => {
3731            input = json!({
3732                "expose": [
3733                    {
3734                        "protocol": "fuchsia.logger.Log",
3735                        "from": "#logger",
3736                        "as": "fuchsia.logger.LegacyLog_default",
3737                        "to": "parent"
3738                    },
3739                    {
3740                        "protocol": "fuchsia.logger.Log",
3741                        "from": "#logger",
3742                        "as": "fuchsia.logger.LegacyLog_required",
3743                        "to": "parent",
3744                        "availability": "required"
3745                    },
3746                    {
3747                        "protocol": "fuchsia.logger.Log",
3748                        "from": "#logger",
3749                        "as": "fuchsia.logger.LegacyLog_optional",
3750                        "to": "parent",
3751                        "availability": "optional"
3752                    },
3753                    {
3754                        "protocol": "fuchsia.logger.Log",
3755                        "from": "#logger",
3756                        "as": "fuchsia.logger.LegacyLog_same_as_target",
3757                        "to": "parent",
3758                        "availability": "same_as_target"
3759                    },
3760                    {
3761                        "protocol": "fuchsia.logger.Log",
3762                        "from": "#logger",
3763                        "as": "fuchsia.logger.LegacyLog_transitional",
3764                        "to": "parent",
3765                        "availability": "transitional"
3766                    },
3767                ],
3768                "children": [
3769                    {
3770                        "name": "logger",
3771                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
3772                    },
3773                ],
3774            }),
3775            output = fdecl::Component {
3776                exposes: Some(vec![
3777                    fdecl::Expose::Protocol (
3778                        fdecl::ExposeProtocol {
3779                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3780                                name: "logger".to_string(),
3781                                collection: None,
3782                            })),
3783                            source_name: Some("fuchsia.logger.Log".to_string()),
3784                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3785                            target_name: Some("fuchsia.logger.LegacyLog_default".to_string()),
3786                            availability: Some(fdecl::Availability::Required),
3787                            ..Default::default()
3788                        }
3789                    ),
3790                    fdecl::Expose::Protocol (
3791                        fdecl::ExposeProtocol {
3792                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3793                                name: "logger".to_string(),
3794                                collection: None,
3795                            })),
3796                            source_name: Some("fuchsia.logger.Log".to_string()),
3797                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3798                            target_name: Some("fuchsia.logger.LegacyLog_required".to_string()),
3799                            availability: Some(fdecl::Availability::Required),
3800                            ..Default::default()
3801                        }
3802                    ),
3803                    fdecl::Expose::Protocol (
3804                        fdecl::ExposeProtocol {
3805                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3806                                name: "logger".to_string(),
3807                                collection: None,
3808                            })),
3809                            source_name: Some("fuchsia.logger.Log".to_string()),
3810                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3811                            target_name: Some("fuchsia.logger.LegacyLog_optional".to_string()),
3812                            availability: Some(fdecl::Availability::Optional),
3813                            ..Default::default()
3814                        }
3815                    ),
3816                    fdecl::Expose::Protocol (
3817                        fdecl::ExposeProtocol {
3818                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3819                                name: "logger".to_string(),
3820                                collection: None,
3821                            })),
3822                            source_name: Some("fuchsia.logger.Log".to_string()),
3823                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3824                            target_name: Some("fuchsia.logger.LegacyLog_same_as_target".to_string()),
3825                            availability: Some(fdecl::Availability::SameAsTarget),
3826                            ..Default::default()
3827                        }
3828                    ),
3829                    fdecl::Expose::Protocol (
3830                        fdecl::ExposeProtocol {
3831                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3832                                name: "logger".to_string(),
3833                                collection: None,
3834                            })),
3835                            source_name: Some("fuchsia.logger.Log".to_string()),
3836                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3837                            target_name: Some("fuchsia.logger.LegacyLog_transitional".to_string()),
3838                            availability: Some(fdecl::Availability::Transitional),
3839                            ..Default::default()
3840                        }
3841                    ),
3842                ]),
3843                offers: None,
3844                capabilities: None,
3845                children: Some(vec![
3846                    fdecl::Child {
3847                        name: Some("logger".to_string()),
3848                        url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
3849                        startup: Some(fdecl::StartupMode::Lazy),
3850                        environment: None,
3851                        on_terminate: None,
3852                        ..Default::default()
3853                    }
3854                ]),
3855                ..default_component_decl()
3856            },
3857        },
3858
3859        test_compile_expose_source_availability_unknown => {
3860            input = json!({
3861                "expose": [
3862                    {
3863                        "protocol": "fuchsia.logger.Log",
3864                        "from": "#non-existent",
3865                        "as": "fuchsia.logger.LegacyLog_non_existent",
3866                        "availability": "optional",
3867                        "source_availability": "unknown"
3868                    },
3869                    {
3870                        "protocol": "fuchsia.logger.Log",
3871                        "from": "#non-existent/dict",
3872                        "as": "fuchsia.logger.LegacyLog_non_existent2",
3873                        "availability": "optional",
3874                        "source_availability": "unknown"
3875                    },
3876                    {
3877                        "protocol": "fuchsia.logger.Log",
3878                        "from": "#logger",
3879                        "as": "fuchsia.logger.LegacyLog_child_exist",
3880                        "availability": "optional",
3881                        "source_availability": "unknown"
3882                    },
3883                ],
3884                "children": [
3885                    {
3886                        "name": "logger",
3887                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
3888                    },
3889                ],
3890            }),
3891            output = fdecl::Component {
3892                exposes: Some(vec![
3893                    fdecl::Expose::Protocol (
3894                        fdecl::ExposeProtocol {
3895                            source: Some(fdecl::Ref::VoidType(fdecl::VoidRef { })),
3896                            source_name: Some("fuchsia.logger.Log".to_string()),
3897                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3898                            target_name: Some("fuchsia.logger.LegacyLog_non_existent".to_string()),
3899                            availability: Some(fdecl::Availability::Optional),
3900                            ..Default::default()
3901                        }
3902                    ),
3903                    fdecl::Expose::Protocol (
3904                        fdecl::ExposeProtocol {
3905                            source: Some(fdecl::Ref::VoidType(fdecl::VoidRef { })),
3906                            source_name: Some("fuchsia.logger.Log".to_string()),
3907                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3908                            target_name: Some("fuchsia.logger.LegacyLog_non_existent2".to_string()),
3909                            availability: Some(fdecl::Availability::Optional),
3910                            ..Default::default()
3911                        }
3912                    ),
3913                    fdecl::Expose::Protocol (
3914                        fdecl::ExposeProtocol {
3915                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
3916                                name: "logger".to_string(),
3917                                collection: None,
3918                            })),
3919                            source_name: Some("fuchsia.logger.Log".to_string()),
3920                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
3921                            target_name: Some("fuchsia.logger.LegacyLog_child_exist".to_string()),
3922                            availability: Some(fdecl::Availability::Optional),
3923                            ..Default::default()
3924                        }
3925                    ),
3926                ]),
3927                children: Some(vec![
3928                    fdecl::Child {
3929                        name: Some("logger".to_string()),
3930                        url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
3931                        startup: Some(fdecl::StartupMode::Lazy),
3932                        ..Default::default()
3933                    }
3934                ]),
3935                ..default_component_decl()
3936            },
3937        },
3938
3939        test_compile_offer_source_availability_unknown => {
3940            input = json!({
3941                "offer": [
3942                    {
3943                        "protocol": "fuchsia.logger.Log",
3944                        "from": "#non-existent",
3945                        "as": "fuchsia.logger.LegacyLog_non_existent",
3946                        "to": "#target",
3947                        "availability": "optional",
3948                        "source_availability": "unknown"
3949                    },
3950                    {
3951                        "protocol": "fuchsia.logger.Log",
3952                        "from": "#non-existent/dict",
3953                        "as": "fuchsia.logger.LegacyLog_non_existent2",
3954                        "to": "#target",
3955                        "availability": "optional",
3956                        "source_availability": "unknown"
3957                    },
3958                    {
3959                        "protocol": "fuchsia.logger.Log",
3960                        "from": "#logger",
3961                        "as": "fuchsia.logger.LegacyLog_child_exist",
3962                        "to": "#target",
3963                        "availability": "optional",
3964                        "source_availability": "unknown"
3965                    },
3966                ],
3967                "children": [
3968                    {
3969                        "name": "logger",
3970                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
3971                    },
3972                    {
3973                        "name": "target",
3974                        "url": "#meta/target.cm"
3975                    },
3976                ],
3977            }),
3978            output = fdecl::Component {
3979                offers: Some(vec![
3980                    fdecl::Offer::Protocol (
3981                        fdecl::OfferProtocol {
3982                            source: Some(fdecl::Ref::VoidType(fdecl::VoidRef { })),
3983                            source_name: Some("fuchsia.logger.Log".to_string()),
3984                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
3985                                name: "target".to_string(),
3986                                collection: None,
3987                            })),
3988                            target_name: Some("fuchsia.logger.LegacyLog_non_existent".to_string()),
3989                            dependency_type: Some(fdecl::DependencyType::Strong),
3990                            availability: Some(fdecl::Availability::Optional),
3991                            ..Default::default()
3992                        }
3993                    ),
3994                    fdecl::Offer::Protocol (
3995                        fdecl::OfferProtocol {
3996                            source: Some(fdecl::Ref::VoidType(fdecl::VoidRef { })),
3997                            source_name: Some("fuchsia.logger.Log".to_string()),
3998                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
3999                                name: "target".to_string(),
4000                                collection: None,
4001                            })),
4002                            target_name: Some("fuchsia.logger.LegacyLog_non_existent2".to_string()),
4003                            dependency_type: Some(fdecl::DependencyType::Strong),
4004                            availability: Some(fdecl::Availability::Optional),
4005                            ..Default::default()
4006                        }
4007                    ),
4008                    fdecl::Offer::Protocol (
4009                        fdecl::OfferProtocol {
4010                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
4011                                name: "logger".to_string(),
4012                                collection: None,
4013                            })),
4014                            source_name: Some("fuchsia.logger.Log".to_string()),
4015                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4016                                name: "target".to_string(),
4017                                collection: None,
4018                            })),
4019                            target_name: Some("fuchsia.logger.LegacyLog_child_exist".to_string()),
4020                            dependency_type: Some(fdecl::DependencyType::Strong),
4021                            availability: Some(fdecl::Availability::Optional),
4022                            ..Default::default()
4023                        }
4024                    ),
4025                ]),
4026                children: Some(vec![
4027                    fdecl::Child {
4028                        name: Some("logger".to_string()),
4029                        url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
4030                        startup: Some(fdecl::StartupMode::Lazy),
4031                        ..Default::default()
4032                    },
4033                    fdecl::Child {
4034                        name: Some("target".to_string()),
4035                        url: Some("#meta/target.cm".to_string()),
4036                        startup: Some(fdecl::StartupMode::Lazy),
4037                        ..Default::default()
4038                    },
4039                ]),
4040                ..default_component_decl()
4041            },
4042        },
4043
4044        test_compile_offer => {
4045            input = json!({
4046                "offer": [
4047                    {
4048                        "protocol": "fuchsia.logger.LegacyLog",
4049                        "from": "#logger",
4050                        "to": "#netstack", // Verifies compilation of singleton "to:".
4051                        "dependency": "weak"
4052                    },
4053                    {
4054                        "protocol": "fuchsia.logger.LegacyLog",
4055                        "from": "#logger",
4056                        "to": [ "#modular" ], // Verifies compilation of "to:" as array of one element.
4057                        "as": "fuchsia.logger.LegacySysLog",
4058                        "dependency": "strong"
4059                    },
4060                    {
4061                        "protocol": [
4062                            "fuchsia.setui.SetUiService",
4063                            "fuchsia.test.service.Name"
4064                        ],
4065                        "from": "parent",
4066                        "to": [ "#modular" ],
4067                        "availability": "optional"
4068                    },
4069                    {
4070                        "protocol": "fuchsia.sys2.StorageAdmin",
4071                        "from": "#data",
4072                        "to": [ "#modular" ],
4073                    },
4074                    {
4075                        "protocol": "fuchsia.sys2.FromDict",
4076                        "from": "parent/in/dict",
4077                        "to": [ "#modular" ],
4078                    },
4079                    {
4080                        "service": "svc",
4081                        "from": [ "parent", "self", "#logger", "#modular" ],
4082                        "to": "#netstack",
4083                    },
4084                    {
4085                        "service": "fuchsia.sys2.FromDictService",
4086                        "from": [ "parent/in/dict"],
4087                        "to": "#modular",
4088                        "dependency": "weak",
4089                    },
4090                    {
4091                        "directory": "assets",
4092                        "from": "parent",
4093                        "to": [ "#netstack" ],
4094                        "dependency": "weak",
4095                        "availability": "same_as_target"
4096                    },
4097                    {
4098                        "directory": [ "assets2", "assets3" ],
4099                        "from": "parent",
4100                        "to": [ "#modular", "#netstack" ],
4101                    },
4102                    {
4103                        "directory": "data",
4104                        "from": "parent",
4105                        "to": [ "#modular" ],
4106                        "as": "assets",
4107                        "subdir": "index/file",
4108                        "dependency": "strong"
4109                    },
4110                    {
4111                        "directory": "hub",
4112                        "from": "framework",
4113                        "to": [ "#modular" ],
4114                        "as": "hub",
4115                    },
4116                    {
4117                        "storage": "data",
4118                        "from": "self",
4119                        "to": [
4120                            "#netstack",
4121                            "#modular"
4122                        ],
4123                    },
4124                    {
4125                        "storage": [ "storage_a", "storage_b" ],
4126                        "from": "parent",
4127                        "to": "#netstack",
4128                    },
4129                    {
4130                        "runner": "elf",
4131                        "from": "parent",
4132                        "to": [ "#modular" ],
4133                        "as": "elf-renamed",
4134                    },
4135                    {
4136                        "runner": [ "runner_a", "runner_b" ],
4137                        "from": "parent",
4138                        "to": "#netstack",
4139                    },
4140                    {
4141                        "resolver": "my_resolver",
4142                        "from": "parent",
4143                        "to": [ "#modular" ],
4144                        "as": "pkg_resolver",
4145                    },
4146                    {
4147                        "resolver": [ "resolver_a", "resolver_b" ],
4148                        "from": "parent",
4149                        "to": "#netstack",
4150                    },
4151                    {
4152                        "dictionary": [ "dictionary_a", "dictionary_b" ],
4153                        "from": "parent",
4154                        "to": "#netstack",
4155                    },
4156                    {
4157                        "event_stream": [
4158                            "running",
4159                            "started",
4160                        ],
4161                        "from": "parent",
4162                        "to": "#netstack",
4163                    },
4164                    {
4165                        "event_stream": "stopped",
4166                        "from": "parent",
4167                        "to": "#netstack",
4168                        "as": "some_other_event",
4169                    },
4170                ],
4171                "children": [
4172                    {
4173                        "name": "logger",
4174                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
4175                    },
4176                    {
4177                        "name": "netstack",
4178                        "url": "fuchsia-pkg://fuchsia.com/netstack/stable#meta/netstack.cm"
4179                    },
4180                ],
4181                "collections": [
4182                    {
4183                        "name": "modular",
4184                        "durability": "transient",
4185                    },
4186                ],
4187                "capabilities": [
4188                    {
4189                        "service": "svc",
4190                    },
4191                    {
4192                        "storage": "data",
4193                        "backing_dir": "minfs",
4194                        "from": "#logger",
4195                        "storage_id": "static_instance_id_or_moniker",
4196                    },
4197                ],
4198            }),
4199            output = fdecl::Component {
4200                offers: Some(vec![
4201                    fdecl::Offer::Protocol (
4202                        fdecl::OfferProtocol {
4203                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
4204                                name: "logger".to_string(),
4205                                collection: None,
4206                            })),
4207                            source_name: Some("fuchsia.logger.LegacyLog".to_string()),
4208                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4209                                name: "netstack".to_string(),
4210                                collection: None,
4211                            })),
4212                            target_name: Some("fuchsia.logger.LegacyLog".to_string()),
4213                            dependency_type: Some(fdecl::DependencyType::Weak),
4214                            availability: Some(fdecl::Availability::Required),
4215                            ..Default::default()
4216                        }
4217                    ),
4218                    fdecl::Offer::Protocol (
4219                        fdecl::OfferProtocol {
4220                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
4221                                name: "logger".to_string(),
4222                                collection: None,
4223                            })),
4224                            source_name: Some("fuchsia.logger.LegacyLog".to_string()),
4225                            target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
4226                                name: "modular".to_string(),
4227                            })),
4228                            target_name: Some("fuchsia.logger.LegacySysLog".to_string()),
4229                            dependency_type: Some(fdecl::DependencyType::Strong),
4230                            availability: Some(fdecl::Availability::Required),
4231                            ..Default::default()
4232                        }
4233                    ),
4234                    fdecl::Offer::Protocol (
4235                        fdecl::OfferProtocol {
4236                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4237                            source_name: Some("fuchsia.setui.SetUiService".to_string()),
4238                            target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
4239                                name: "modular".to_string(),
4240                            })),
4241                            target_name: Some("fuchsia.setui.SetUiService".to_string()),
4242                            dependency_type: Some(fdecl::DependencyType::Strong),
4243                            availability: Some(fdecl::Availability::Optional),
4244                            ..Default::default()
4245                        }
4246                    ),
4247                    fdecl::Offer::Protocol (
4248                        fdecl::OfferProtocol {
4249                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4250                            source_name: Some("fuchsia.test.service.Name".to_string()),
4251                            target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
4252                                name: "modular".to_string(),
4253                            })),
4254                            target_name: Some("fuchsia.test.service.Name".to_string()),
4255                            dependency_type: Some(fdecl::DependencyType::Strong),
4256                            availability: Some(fdecl::Availability::Optional),
4257                            ..Default::default()
4258                        }
4259                    ),
4260                    fdecl::Offer::Protocol (
4261                        fdecl::OfferProtocol {
4262                            source: Some(fdecl::Ref::Capability(fdecl::CapabilityRef {
4263                                name: "data".to_string(),
4264                            })),
4265                            source_name: Some("fuchsia.sys2.StorageAdmin".to_string()),
4266                            target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
4267                                name: "modular".to_string(),
4268                            })),
4269                            target_name: Some("fuchsia.sys2.StorageAdmin".to_string()),
4270                            dependency_type: Some(fdecl::DependencyType::Strong),
4271                            availability: Some(fdecl::Availability::Required),
4272                            ..Default::default()
4273                        }
4274                    ),
4275                    fdecl::Offer::Protocol (
4276                        fdecl::OfferProtocol {
4277                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4278                            #[cfg(fuchsia_api_level_at_least = "25")]
4279                            source_dictionary: Some("in/dict".into()),
4280                            source_name: Some("fuchsia.sys2.FromDict".to_string()),
4281                            target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
4282                                name: "modular".to_string(),
4283                            })),
4284                            target_name: Some("fuchsia.sys2.FromDict".to_string()),
4285                            dependency_type: Some(fdecl::DependencyType::Strong),
4286                            availability: Some(fdecl::Availability::Required),
4287                            ..Default::default()
4288                        }
4289                    ),
4290                    fdecl::Offer::Service (
4291                        fdecl::OfferService {
4292                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4293                            source_name: Some("svc".into()),
4294                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4295                                name: "netstack".into(),
4296                                collection: None,
4297                            })),
4298                            target_name: Some("svc".into()),
4299                            availability: Some(fdecl::Availability::Required),
4300                            dependency_type: Some(fdecl::DependencyType::Strong),
4301                            ..Default::default()
4302                        }
4303                    ),
4304                    fdecl::Offer::Service (
4305                        fdecl::OfferService {
4306                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
4307                            source_name: Some("svc".into()),
4308                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4309                                name: "netstack".into(),
4310                                collection: None,
4311                            })),
4312                            target_name: Some("svc".into()),
4313                            availability: Some(fdecl::Availability::Required),
4314                            dependency_type: Some(fdecl::DependencyType::Strong),
4315                            ..Default::default()
4316                        }
4317                    ),
4318                    fdecl::Offer::Service (
4319                        fdecl::OfferService {
4320                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
4321                                name: "logger".into(),
4322                                collection: None,
4323                            })),
4324                            source_name: Some("svc".into()),
4325                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4326                                name: "netstack".into(),
4327                                collection: None,
4328                            })),
4329                            target_name: Some("svc".into()),
4330                            availability: Some(fdecl::Availability::Required),
4331                            dependency_type: Some(fdecl::DependencyType::Strong),
4332                            ..Default::default()
4333                        }
4334                    ),
4335                    fdecl::Offer::Service (
4336                        fdecl::OfferService {
4337                            source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
4338                                name: "modular".into(),
4339                            })),
4340                            source_name: Some("svc".into()),
4341                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4342                                name: "netstack".into(),
4343                                collection: None,
4344                            })),
4345                            target_name: Some("svc".into()),
4346                            availability: Some(fdecl::Availability::Required),
4347                            dependency_type: Some(fdecl::DependencyType::Strong),
4348                            ..Default::default()
4349                        }
4350                    ),
4351                    fdecl::Offer::Service (
4352                        fdecl::OfferService {
4353                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4354                            source_name: Some("fuchsia.sys2.FromDictService".into()),
4355                            #[cfg(fuchsia_api_level_at_least = "25")]
4356                            source_dictionary: Some("in/dict".into()),
4357                            target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
4358                                name: "modular".into(),
4359                            })),
4360                            target_name: Some("fuchsia.sys2.FromDictService".to_string()),
4361                            availability: Some(fdecl::Availability::Required),
4362                            dependency_type: Some(fdecl::DependencyType::Weak),
4363                            ..Default::default()
4364                        }
4365                    ),
4366                    fdecl::Offer::Directory (
4367                        fdecl::OfferDirectory {
4368                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4369                            source_name: Some("assets".to_string()),
4370                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4371                                name: "netstack".to_string(),
4372                                collection: None,
4373                            })),
4374                            target_name: Some("assets".to_string()),
4375                            rights: None,
4376                            subdir: None,
4377                            dependency_type: Some(fdecl::DependencyType::Weak),
4378                            availability: Some(fdecl::Availability::SameAsTarget),
4379                            ..Default::default()
4380                        }
4381                    ),
4382                    fdecl::Offer::Directory (
4383                        fdecl::OfferDirectory {
4384                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4385                            source_name: Some("assets2".to_string()),
4386                            target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
4387                                name: "modular".to_string(),
4388                            })),
4389                            target_name: Some("assets2".to_string()),
4390                            rights: None,
4391                            subdir: None,
4392                            dependency_type: Some(fdecl::DependencyType::Strong),
4393                            availability: Some(fdecl::Availability::Required),
4394                            ..Default::default()
4395                        }
4396                    ),
4397                    fdecl::Offer::Directory (
4398                        fdecl::OfferDirectory {
4399                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4400                            source_name: Some("assets3".to_string()),
4401                            target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
4402                                name: "modular".to_string(),
4403                            })),
4404                            target_name: Some("assets3".to_string()),
4405                            rights: None,
4406                            subdir: None,
4407                            dependency_type: Some(fdecl::DependencyType::Strong),
4408                            availability: Some(fdecl::Availability::Required),
4409                            ..Default::default()
4410                        }
4411                    ),
4412                    fdecl::Offer::Directory (
4413                        fdecl::OfferDirectory {
4414                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4415                            source_name: Some("assets2".to_string()),
4416                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4417                                name: "netstack".to_string(),
4418                                collection: None,
4419                            })),
4420                            target_name: Some("assets2".to_string()),
4421                            rights: None,
4422                            subdir: None,
4423                            dependency_type: Some(fdecl::DependencyType::Strong),
4424                            availability: Some(fdecl::Availability::Required),
4425                            ..Default::default()
4426                        }
4427                    ),
4428                    fdecl::Offer::Directory (
4429                        fdecl::OfferDirectory {
4430                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4431                            source_name: Some("assets3".to_string()),
4432                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4433                                name: "netstack".to_string(),
4434                                collection: None,
4435                            })),
4436                            target_name: Some("assets3".to_string()),
4437                            rights: None,
4438                            subdir: None,
4439                            dependency_type: Some(fdecl::DependencyType::Strong),
4440                            availability: Some(fdecl::Availability::Required),
4441                            ..Default::default()
4442                        }
4443                    ),
4444                    fdecl::Offer::Directory (
4445                        fdecl::OfferDirectory {
4446                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4447                            source_name: Some("data".to_string()),
4448                            target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
4449                                name: "modular".to_string(),
4450                            })),
4451                            target_name: Some("assets".to_string()),
4452                            rights: None,
4453                            subdir: Some("index/file".to_string()),
4454                            dependency_type: Some(fdecl::DependencyType::Strong),
4455                            availability: Some(fdecl::Availability::Required),
4456                            ..Default::default()
4457                        }
4458                    ),
4459                    fdecl::Offer::Directory (
4460                        fdecl::OfferDirectory {
4461                            source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef {})),
4462                            source_name: Some("hub".to_string()),
4463                            target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
4464                                name: "modular".to_string(),
4465                            })),
4466                            target_name: Some("hub".to_string()),
4467                            rights: None,
4468                            subdir: None,
4469                            dependency_type: Some(fdecl::DependencyType::Strong),
4470                            availability: Some(fdecl::Availability::Required),
4471                            ..Default::default()
4472                        }
4473                    ),
4474                    fdecl::Offer::Storage (
4475                        fdecl::OfferStorage {
4476                            source_name: Some("data".to_string()),
4477                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
4478                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4479                                name: "netstack".to_string(),
4480                                collection: None,
4481                            })),
4482                            target_name: Some("data".to_string()),
4483                            availability: Some(fdecl::Availability::Required),
4484                            ..Default::default()
4485                        }
4486                    ),
4487                    fdecl::Offer::Storage (
4488                        fdecl::OfferStorage {
4489                            source_name: Some("data".to_string()),
4490                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
4491                            target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
4492                                name: "modular".to_string(),
4493                            })),
4494                            target_name: Some("data".to_string()),
4495                            availability: Some(fdecl::Availability::Required),
4496                            ..Default::default()
4497                        }
4498                    ),
4499                    fdecl::Offer::Storage (
4500                        fdecl::OfferStorage {
4501                            source_name: Some("storage_a".to_string()),
4502                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4503                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4504                                name: "netstack".to_string(),
4505                                collection: None,
4506                            })),
4507                            target_name: Some("storage_a".to_string()),
4508                            availability: Some(fdecl::Availability::Required),
4509                            ..Default::default()
4510                        }
4511                    ),
4512                    fdecl::Offer::Storage (
4513                        fdecl::OfferStorage {
4514                            source_name: Some("storage_b".to_string()),
4515                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4516                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4517                                name: "netstack".to_string(),
4518                                collection: None,
4519                            })),
4520                            target_name: Some("storage_b".to_string()),
4521                            availability: Some(fdecl::Availability::Required),
4522                            ..Default::default()
4523                        }
4524                    ),
4525                    fdecl::Offer::Runner (
4526                        fdecl::OfferRunner {
4527                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4528                            source_name: Some("elf".to_string()),
4529                            target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
4530                                name: "modular".to_string(),
4531                            })),
4532                            target_name: Some("elf-renamed".to_string()),
4533                            ..Default::default()
4534                        }
4535                    ),
4536                    fdecl::Offer::Runner (
4537                        fdecl::OfferRunner {
4538                            source_name: Some("runner_a".to_string()),
4539                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4540                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4541                                name: "netstack".to_string(),
4542                                collection: None,
4543                            })),
4544                            target_name: Some("runner_a".to_string()),
4545                            ..Default::default()
4546                        }
4547                    ),
4548                    fdecl::Offer::Runner (
4549                        fdecl::OfferRunner {
4550                            source_name: Some("runner_b".to_string()),
4551                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4552                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4553                                name: "netstack".to_string(),
4554                                collection: None,
4555                            })),
4556                            target_name: Some("runner_b".to_string()),
4557                            ..Default::default()
4558                        }
4559                    ),
4560                    fdecl::Offer::Resolver (
4561                        fdecl::OfferResolver {
4562                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4563                            source_name: Some("my_resolver".to_string()),
4564                            target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
4565                                name: "modular".to_string(),
4566                            })),
4567                            target_name: Some("pkg_resolver".to_string()),
4568                            ..Default::default()
4569                        }
4570                    ),
4571                    fdecl::Offer::Resolver (
4572                        fdecl::OfferResolver {
4573                            source_name: Some("resolver_a".to_string()),
4574                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4575                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4576                                name: "netstack".to_string(),
4577                                collection: None,
4578                            })),
4579                            target_name: Some("resolver_a".to_string()),
4580                            ..Default::default()
4581                        }
4582                    ),
4583                    fdecl::Offer::Resolver (
4584                        fdecl::OfferResolver {
4585                            source_name: Some("resolver_b".to_string()),
4586                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4587                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4588                                name: "netstack".to_string(),
4589                                collection: None,
4590                            })),
4591                            target_name: Some("resolver_b".to_string()),
4592                            ..Default::default()
4593                        }
4594                    ),
4595                    fdecl::Offer::Dictionary (
4596                        fdecl::OfferDictionary {
4597                            source_name: Some("dictionary_a".to_string()),
4598                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4599                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4600                                name: "netstack".to_string(),
4601                                collection: None,
4602                            })),
4603                            target_name: Some("dictionary_a".to_string()),
4604                            dependency_type: Some(fdecl::DependencyType::Strong),
4605                            availability: Some(fdecl::Availability::Required),
4606                            ..Default::default()
4607                        }
4608                    ),
4609                    fdecl::Offer::Dictionary (
4610                        fdecl::OfferDictionary {
4611                            source_name: Some("dictionary_b".to_string()),
4612                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4613                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4614                                name: "netstack".to_string(),
4615                                collection: None,
4616                            })),
4617                            target_name: Some("dictionary_b".to_string()),
4618                            dependency_type: Some(fdecl::DependencyType::Strong),
4619                            availability: Some(fdecl::Availability::Required),
4620                            ..Default::default()
4621                        }
4622                    ),
4623                    fdecl::Offer::EventStream (
4624                        fdecl::OfferEventStream {
4625                            source_name: Some("running".to_string()),
4626                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4627                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4628                                name: "netstack".to_string(),
4629                                collection: None,
4630                            })),
4631                            target_name: Some("running".to_string()),
4632                            availability: Some(fdecl::Availability::Required),
4633                            ..Default::default()
4634                        }
4635                    ),
4636                    fdecl::Offer::EventStream (
4637                        fdecl::OfferEventStream {
4638                            source_name: Some("started".to_string()),
4639                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4640                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4641                                name: "netstack".to_string(),
4642                                collection: None,
4643                            })),
4644                            target_name: Some("started".to_string()),
4645                            availability: Some(fdecl::Availability::Required),
4646                            ..Default::default()
4647                        }
4648                    ),
4649                    fdecl::Offer::EventStream (
4650                        fdecl::OfferEventStream {
4651                            source_name: Some("stopped".to_string()),
4652                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4653                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
4654                                name: "netstack".to_string(),
4655                                collection: None,
4656                            })),
4657                            target_name: Some("some_other_event".to_string()),
4658                            availability: Some(fdecl::Availability::Required),
4659                            ..Default::default()
4660                        }
4661                    ),
4662                ]),
4663                capabilities: Some(vec![
4664                    fdecl::Capability::Service (
4665                        fdecl::Service {
4666                            name: Some("svc".into()),
4667                            source_path: Some("/svc/svc".into()),
4668                            ..Default::default()
4669                        },
4670                    ),
4671                    fdecl::Capability::Storage (
4672                        fdecl::Storage {
4673                            name: Some("data".to_string()),
4674                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
4675                                name: "logger".to_string(),
4676                                collection: None,
4677                            })),
4678                            backing_dir: Some("minfs".to_string()),
4679                            subdir: None,
4680                            storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker),
4681                            ..Default::default()
4682                        }
4683                    )
4684                ]),
4685                children: Some(vec![
4686                    fdecl::Child {
4687                        name: Some("logger".to_string()),
4688                        url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
4689                        startup: Some(fdecl::StartupMode::Lazy),
4690                        environment: None,
4691                        on_terminate: None,
4692                        ..Default::default()
4693                    },
4694                    fdecl::Child {
4695                        name: Some("netstack".to_string()),
4696                        url: Some("fuchsia-pkg://fuchsia.com/netstack/stable#meta/netstack.cm".to_string()),
4697                        startup: Some(fdecl::StartupMode::Lazy),
4698                        environment: None,
4699                        on_terminate: None,
4700                        ..Default::default()
4701                    },
4702                ]),
4703                collections: Some(vec![
4704                    fdecl::Collection {
4705                        name: Some("modular".to_string()),
4706                        durability: Some(fdecl::Durability::Transient),
4707                        environment: None,
4708                        allowed_offers: None,
4709                        ..Default::default()
4710                    }
4711                ]),
4712                ..default_component_decl()
4713            },
4714        },
4715
4716        test_compile_offer_route_to_dictionary => {
4717            input = json!({
4718                "offer": [
4719                    {
4720                        "protocol": "A",
4721                        "from": "parent/dict/1",
4722                        "to": "self/dict",
4723                    },
4724                    {
4725                        "runner": "B",
4726                        "from": "#child",
4727                        "to": "self/dict",
4728                    },
4729                    {
4730                        "config": "B",
4731                        "from": "parent/dict/2",
4732                        "to": "self/dict",
4733                        "as": "C",
4734                    },
4735                ],
4736                "children": [
4737                    {
4738                        "name": "child",
4739                        "url": "fuchsia-pkg://child"
4740                    },
4741                ],
4742                "capabilities": [
4743                    {
4744                        "dictionary": "dict",
4745                    },
4746                ],
4747            }),
4748            output = fdecl::Component {
4749                offers: Some(vec![
4750                    fdecl::Offer::Protocol (
4751                        fdecl::OfferProtocol {
4752                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4753                            #[cfg(fuchsia_api_level_at_least = "25")]
4754                            source_dictionary: Some("dict/1".into()),
4755                            source_name: Some("A".into()),
4756                            target: Some(fdecl::Ref::Capability(fdecl::CapabilityRef {
4757                                name: "dict".to_string(),
4758                            })),
4759                            target_name: Some("A".into()),
4760                            dependency_type: Some(fdecl::DependencyType::Strong),
4761                            availability: Some(fdecl::Availability::Required),
4762                            ..Default::default()
4763                        }
4764                    ),
4765                    fdecl::Offer::Runner (
4766                        fdecl::OfferRunner {
4767                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
4768                                name: "child".into(),
4769                                collection: None,
4770                            })),
4771                            source_name: Some("B".into()),
4772                            target: Some(fdecl::Ref::Capability(fdecl::CapabilityRef {
4773                                name: "dict".to_string(),
4774                            })),
4775                            target_name: Some("B".into()),
4776                            ..Default::default()
4777                        }
4778                    ),
4779                    fdecl::Offer::Config (
4780                        fdecl::OfferConfiguration {
4781                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
4782                            #[cfg(fuchsia_api_level_at_least = "25")]
4783                            source_dictionary: Some("dict/2".into()),
4784                            source_name: Some("B".into()),
4785                            target: Some(fdecl::Ref::Capability(fdecl::CapabilityRef {
4786                                name: "dict".to_string(),
4787                            })),
4788                            target_name: Some("C".into()),
4789                            availability: Some(fdecl::Availability::Required),
4790                            ..Default::default()
4791                        }
4792                    ),
4793                ]),
4794                capabilities: Some(vec![
4795                    fdecl::Capability::Dictionary (
4796                        fdecl::Dictionary {
4797                            name: Some("dict".into()),
4798                            ..Default::default()
4799                        }
4800                    )
4801                ]),
4802                children: Some(vec![
4803                    fdecl::Child {
4804                        name: Some("child".to_string()),
4805                        url: Some("fuchsia-pkg://child".to_string()),
4806                        startup: Some(fdecl::StartupMode::Lazy),
4807                        ..Default::default()
4808                    },
4809                ]),
4810                ..default_component_decl()
4811            },
4812        },
4813
4814
4815        test_compile_children => {
4816            input = json!({
4817                "children": [
4818                    {
4819                        "name": "logger",
4820                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
4821                    },
4822                    {
4823                        "name": "gmail",
4824                        "url": "https://www.google.com/gmail",
4825                        "startup": "eager",
4826                    },
4827                    {
4828                        "name": "echo",
4829                        "url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo.cm",
4830                        "startup": "lazy",
4831                        "on_terminate": "reboot",
4832                        "environment": "#myenv",
4833                    },
4834                ],
4835                "environments": [
4836                    {
4837                        "name": "myenv",
4838                        "extends": "realm",
4839                    },
4840                ],
4841            }),
4842            output = fdecl::Component {
4843                children: Some(vec![
4844                    fdecl::Child {
4845                        name: Some("logger".to_string()),
4846                        url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
4847                        startup: Some(fdecl::StartupMode::Lazy),
4848                        environment: None,
4849                        on_terminate: None,
4850                        ..Default::default()
4851                    },
4852                    fdecl::Child {
4853                        name: Some("gmail".to_string()),
4854                        url: Some("https://www.google.com/gmail".to_string()),
4855                        startup: Some(fdecl::StartupMode::Eager),
4856                        environment: None,
4857                        on_terminate: None,
4858                        ..Default::default()
4859                    },
4860                    fdecl::Child {
4861                        name: Some("echo".to_string()),
4862                        url: Some("fuchsia-pkg://fuchsia.com/echo/stable#meta/echo.cm".to_string()),
4863                        startup: Some(fdecl::StartupMode::Lazy),
4864                        environment: Some("myenv".to_string()),
4865                        on_terminate: Some(fdecl::OnTerminate::Reboot),
4866                        ..Default::default()
4867                    }
4868                ]),
4869                environments: Some(vec![
4870                    fdecl::Environment {
4871                        name: Some("myenv".to_string()),
4872                        extends: Some(fdecl::EnvironmentExtends::Realm),
4873                        runners: None,
4874                        resolvers: None,
4875                        stop_timeout_ms: None,
4876                        ..Default::default()
4877                    }
4878                ]),
4879                ..default_component_decl()
4880            },
4881        },
4882
4883        test_compile_collections => {
4884            input = json!({
4885                "collections": [
4886                    {
4887                        "name": "modular",
4888                        "durability": "single_run",
4889                    },
4890                    {
4891                        "name": "tests",
4892                        "durability": "transient",
4893                        "environment": "#myenv",
4894                    },
4895                ],
4896                "environments": [
4897                    {
4898                        "name": "myenv",
4899                        "extends": "realm",
4900                    }
4901                ],
4902            }),
4903            output = fdecl::Component {
4904                collections: Some(vec![
4905                    fdecl::Collection {
4906                        name: Some("modular".to_string()),
4907                        durability: Some(fdecl::Durability::SingleRun),
4908                        environment: None,
4909                        allowed_offers: None,
4910                        ..Default::default()
4911                    },
4912                    fdecl::Collection {
4913                        name: Some("tests".to_string()),
4914                        durability: Some(fdecl::Durability::Transient),
4915                        environment: Some("myenv".to_string()),
4916                        allowed_offers: None,
4917                        ..Default::default()
4918                    }
4919                ]),
4920                environments: Some(vec![
4921                    fdecl::Environment {
4922                        name: Some("myenv".to_string()),
4923                        extends: Some(fdecl::EnvironmentExtends::Realm),
4924                        runners: None,
4925                        resolvers: None,
4926                        stop_timeout_ms: None,
4927                        ..Default::default()
4928                    }
4929                ]),
4930                ..default_component_decl()
4931            },
4932        },
4933
4934        test_compile_capabilities => {
4935            features = FeatureSet::from(vec![Feature::DynamicDictionaries]),
4936            input = json!({
4937                "capabilities": [
4938                    {
4939                        "protocol": "myprotocol",
4940                        "path": "/protocol",
4941                    },
4942                    {
4943                        "protocol": "myprotocol2",
4944                    },
4945                    {
4946                        "protocol": [ "myprotocol3", "myprotocol4" ],
4947                    },
4948                    {
4949                        "directory": "mydirectory",
4950                        "path": "/directory",
4951                        "rights": [ "connect" ],
4952                    },
4953                    {
4954                        "storage": "mystorage",
4955                        "backing_dir": "storage",
4956                        "from": "#minfs",
4957                        "storage_id": "static_instance_id_or_moniker",
4958                    },
4959                    {
4960                        "storage": "mystorage2",
4961                        "backing_dir": "storage2",
4962                        "from": "#minfs",
4963                        "storage_id": "static_instance_id",
4964                    },
4965                    {
4966                        "runner": "myrunner",
4967                        "path": "/runner",
4968                    },
4969                    {
4970                        "resolver": "myresolver",
4971                        "path": "/resolver"
4972                    },
4973                    {
4974                        "dictionary": "dict1",
4975                    },
4976                    {
4977                        "dictionary": "dict2",
4978                        "path": "/in/a",
4979                    },
4980                ],
4981                "children": [
4982                    {
4983                        "name": "minfs",
4984                        "url": "fuchsia-pkg://fuchsia.com/minfs/stable#meta/minfs.cm",
4985                    },
4986                ]
4987            }),
4988            output = fdecl::Component {
4989                capabilities: Some(vec![
4990                    fdecl::Capability::Protocol (
4991                        fdecl::Protocol {
4992                            name: Some("myprotocol".to_string()),
4993                            source_path: Some("/protocol".to_string()),
4994                            ..Default::default()
4995                        }
4996                    ),
4997                    fdecl::Capability::Protocol (
4998                        fdecl::Protocol {
4999                            name: Some("myprotocol2".to_string()),
5000                            source_path: Some("/svc/myprotocol2".to_string()),
5001                            ..Default::default()
5002                        }
5003                    ),
5004                    fdecl::Capability::Protocol (
5005                        fdecl::Protocol {
5006                            name: Some("myprotocol3".to_string()),
5007                            source_path: Some("/svc/myprotocol3".to_string()),
5008                            ..Default::default()
5009                        }
5010                    ),
5011                    fdecl::Capability::Protocol (
5012                        fdecl::Protocol {
5013                            name: Some("myprotocol4".to_string()),
5014                            source_path: Some("/svc/myprotocol4".to_string()),
5015                            ..Default::default()
5016                        }
5017                    ),
5018                    fdecl::Capability::Directory (
5019                        fdecl::Directory {
5020                            name: Some("mydirectory".to_string()),
5021                            source_path: Some("/directory".to_string()),
5022                            rights: Some(fio::Operations::CONNECT),
5023                            ..Default::default()
5024                        }
5025                    ),
5026                    fdecl::Capability::Storage (
5027                        fdecl::Storage {
5028                            name: Some("mystorage".to_string()),
5029                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5030                                name: "minfs".to_string(),
5031                                collection: None,
5032                            })),
5033                            backing_dir: Some("storage".to_string()),
5034                            subdir: None,
5035                            storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker),
5036                            ..Default::default()
5037                        }
5038                    ),
5039                    fdecl::Capability::Storage (
5040                        fdecl::Storage {
5041                            name: Some("mystorage2".to_string()),
5042                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5043                                name: "minfs".to_string(),
5044                                collection: None,
5045                            })),
5046                            backing_dir: Some("storage2".to_string()),
5047                            subdir: None,
5048                            storage_id: Some(fdecl::StorageId::StaticInstanceId),
5049                            ..Default::default()
5050                        }
5051                    ),
5052                    fdecl::Capability::Runner (
5053                        fdecl::Runner {
5054                            name: Some("myrunner".to_string()),
5055                            source_path: Some("/runner".to_string()),
5056                            ..Default::default()
5057                        }
5058                    ),
5059                    fdecl::Capability::Resolver (
5060                        fdecl::Resolver {
5061                            name: Some("myresolver".to_string()),
5062                            source_path: Some("/resolver".to_string()),
5063                            ..Default::default()
5064                        }
5065                    ),
5066                    fdecl::Capability::Dictionary (
5067                        fdecl::Dictionary {
5068                            name: Some("dict1".into()),
5069                            ..Default::default()
5070                        }
5071                    ),
5072                    fdecl::Capability::Dictionary (
5073                        fdecl::Dictionary {
5074                            name: Some("dict2".into()),
5075                            source: None,
5076                            source_dictionary: None,
5077                            source_path: Some("/in/a".into()),
5078                            ..Default::default()
5079                        }
5080                    ),
5081                ]),
5082                children: Some(vec![
5083                    fdecl::Child {
5084                        name: Some("minfs".to_string()),
5085                        url: Some("fuchsia-pkg://fuchsia.com/minfs/stable#meta/minfs.cm".to_string()),
5086                        startup: Some(fdecl::StartupMode::Lazy),
5087                        environment: None,
5088                        on_terminate: None,
5089                        ..Default::default()
5090                    }
5091                ]),
5092                ..default_component_decl()
5093            },
5094        },
5095
5096        test_compile_facets => {
5097            input = json!({
5098                "facets": {
5099                    "title": "foo",
5100                    "authors": [ "me", "you" ],
5101                    "year": "2018",
5102                    "metadata": {
5103                        "publisher": "The Books Publisher",
5104                    }
5105                }
5106            }),
5107            output = fdecl::Component {
5108                facets: Some(fdata::Dictionary {
5109                        entries: Some(vec![
5110                            fdata::DictionaryEntry {
5111                                key: "authors".to_string(),
5112                                value: Some(Box::new(fdata::DictionaryValue::StrVec(vec!["me".to_owned(), "you".to_owned()]))),
5113                            },
5114                            fdata::DictionaryEntry {
5115                                key: "metadata.publisher".to_string(),
5116                                value: Some(Box::new(fdata::DictionaryValue::Str("The Books Publisher".to_string()))),
5117                            },
5118                            fdata::DictionaryEntry {
5119                                key: "title".to_string(),
5120                                value: Some(Box::new(fdata::DictionaryValue::Str("foo".to_string()))),
5121                            },
5122                            fdata::DictionaryEntry {
5123                                key: "year".to_string(),
5124                                value: Some(Box::new(fdata::DictionaryValue::Str("2018".to_string()))),
5125                            },
5126                        ]),
5127                        ..Default::default()
5128                    }
5129            ),
5130            ..default_component_decl()
5131            },
5132        },
5133
5134        test_compile_environment => {
5135            input = json!({
5136                "environments": [
5137                    {
5138                        "name": "myenv",
5139                        "__stop_timeout_ms": 10u32,
5140                    },
5141                    {
5142                        "name": "myenv2",
5143                        "extends": "realm",
5144                    },
5145                    {
5146                        "name": "myenv3",
5147                        "extends": "none",
5148                        "__stop_timeout_ms": 8000u32,
5149                    }
5150                ],
5151            }),
5152            output = fdecl::Component {
5153                environments: Some(vec![
5154                    fdecl::Environment {
5155                        name: Some("myenv".to_string()),
5156                        extends: Some(fdecl::EnvironmentExtends::None),
5157                        runners: None,
5158                        resolvers: None,
5159                        stop_timeout_ms: Some(10),
5160                        ..Default::default()
5161                    },
5162                    fdecl::Environment {
5163                        name: Some("myenv2".to_string()),
5164                        extends: Some(fdecl::EnvironmentExtends::Realm),
5165                        runners: None,
5166                        resolvers: None,
5167                        stop_timeout_ms: None,
5168                        ..Default::default()
5169                    },
5170                    fdecl::Environment {
5171                        name: Some("myenv3".to_string()),
5172                        extends: Some(fdecl::EnvironmentExtends::None),
5173                        runners: None,
5174                        resolvers: None,
5175                        stop_timeout_ms: Some(8000),
5176                        ..Default::default()
5177                    },
5178                ]),
5179                ..default_component_decl()
5180            },
5181        },
5182
5183        test_compile_environment_with_runner_and_resolver => {
5184            input = json!({
5185                "environments": [
5186                    {
5187                        "name": "myenv",
5188                        "extends": "realm",
5189                        "runners": [
5190                            {
5191                                "runner": "dart",
5192                                "from": "parent",
5193                            }
5194                        ],
5195                        "resolvers": [
5196                            {
5197                                "resolver": "pkg_resolver",
5198                                "from": "parent",
5199                                "scheme": "fuchsia-pkg",
5200                            }
5201                        ],
5202                    },
5203                ],
5204            }),
5205            output = fdecl::Component {
5206                environments: Some(vec![
5207                    fdecl::Environment {
5208                        name: Some("myenv".to_string()),
5209                        extends: Some(fdecl::EnvironmentExtends::Realm),
5210                        runners: Some(vec![
5211                            fdecl::RunnerRegistration {
5212                                source_name: Some("dart".to_string()),
5213                                source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5214                                target_name: Some("dart".to_string()),
5215                                ..Default::default()
5216                            }
5217                        ]),
5218                        resolvers: Some(vec![
5219                            fdecl::ResolverRegistration {
5220                                resolver: Some("pkg_resolver".to_string()),
5221                                source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5222                                scheme: Some("fuchsia-pkg".to_string()),
5223                                ..Default::default()
5224                            }
5225                        ]),
5226                        stop_timeout_ms: None,
5227                        ..Default::default()
5228                    },
5229                ]),
5230                ..default_component_decl()
5231            },
5232        },
5233
5234        test_compile_environment_with_runner_alias => {
5235            input = json!({
5236                "environments": [
5237                    {
5238                        "name": "myenv",
5239                        "extends": "realm",
5240                        "runners": [
5241                            {
5242                                "runner": "dart",
5243                                "from": "parent",
5244                                "as": "my-dart",
5245                            }
5246                        ],
5247                    },
5248                ],
5249            }),
5250            output = fdecl::Component {
5251                environments: Some(vec![
5252                    fdecl::Environment {
5253                        name: Some("myenv".to_string()),
5254                        extends: Some(fdecl::EnvironmentExtends::Realm),
5255                        runners: Some(vec![
5256                            fdecl::RunnerRegistration {
5257                                source_name: Some("dart".to_string()),
5258                                source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5259                                target_name: Some("my-dart".to_string()),
5260                                ..Default::default()
5261                            }
5262                        ]),
5263                        resolvers: None,
5264                        stop_timeout_ms: None,
5265                        ..Default::default()
5266                    },
5267                ]),
5268                ..default_component_decl()
5269            },
5270        },
5271
5272        test_compile_environment_with_debug => {
5273            input = json!({
5274                "capabilities": [
5275                    {
5276                        "protocol": "fuchsia.serve.service",
5277                    },
5278                ],
5279                "environments": [
5280                    {
5281                        "name": "myenv",
5282                        "extends": "realm",
5283                        "debug": [
5284                            {
5285                                "protocol": "fuchsia.serve.service",
5286                                "from": "self",
5287                                "as": "my-service",
5288                            }
5289                        ],
5290                    },
5291                ],
5292            }),
5293            output = fdecl::Component {
5294                capabilities: Some(vec![
5295                    fdecl::Capability::Protocol(
5296                        fdecl::Protocol {
5297                            name : Some("fuchsia.serve.service".to_owned()),
5298                            source_path: Some("/svc/fuchsia.serve.service".to_owned()),
5299                            ..Default::default()
5300                        }
5301                    )
5302                ]),
5303                environments: Some(vec![
5304                    fdecl::Environment {
5305                        name: Some("myenv".to_string()),
5306                        extends: Some(fdecl::EnvironmentExtends::Realm),
5307                        debug_capabilities: Some(vec![
5308                            fdecl::DebugRegistration::Protocol( fdecl::DebugProtocolRegistration {
5309                                source_name: Some("fuchsia.serve.service".to_string()),
5310                                source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
5311                                target_name: Some("my-service".to_string()),
5312                                ..Default::default()
5313                            }),
5314                        ]),
5315                        resolvers: None,
5316                        runners: None,
5317                        stop_timeout_ms: None,
5318                        ..Default::default()
5319                    },
5320                ]),
5321                ..default_component_decl()
5322            },
5323        },
5324
5325
5326        test_compile_configuration_capability => {
5327            input = json!({
5328                "capabilities": [
5329                    {
5330                        "config": "fuchsia.config.true",
5331                        "type": "bool",
5332                        "value": true,
5333                    },
5334                    {
5335                        "config": "fuchsia.config.false",
5336                        "type": "bool",
5337                        "value": false,
5338                    },
5339                ],
5340            }),
5341            output = fdecl::Component {
5342                capabilities: Some(vec![
5343                    fdecl::Capability::Config (
5344                        fdecl::Configuration {
5345                            name: Some("fuchsia.config.true".to_string()),
5346                            value: Some(fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Bool(true))),
5347                            ..Default::default()
5348                        }),
5349                    fdecl::Capability::Config (
5350                        fdecl::Configuration {
5351                            name: Some("fuchsia.config.false".to_string()),
5352                            value: Some(fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Bool(false))),
5353                            ..Default::default()
5354                        }),
5355                ]),
5356                ..default_component_decl()
5357            },
5358        },
5359
5360        test_compile_all_sections => {
5361            input = json!({
5362                "program": {
5363                    "runner": "elf",
5364                    "binary": "bin/app",
5365                },
5366                "use": [
5367                    { "protocol": "LegacyCoolFonts", "path": "/svc/fuchsia.fonts.LegacyProvider" },
5368                    { "protocol": [ "ReallyGoodFonts", "IWouldNeverUseTheseFonts"]},
5369                    { "protocol":  "DebugProtocol", "from": "debug"},
5370                ],
5371                "expose": [
5372                    { "directory": "blobfs", "from": "self", "rights": ["r*"]},
5373                ],
5374                "offer": [
5375                    {
5376                        "protocol": "fuchsia.logger.LegacyLog",
5377                        "from": "#logger",
5378                        "to": [ "#netstack", "#modular" ],
5379                        "dependency": "weak"
5380                    },
5381                ],
5382                "children": [
5383                    {
5384                        "name": "logger",
5385                        "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
5386                    },
5387                    {
5388                        "name": "netstack",
5389                        "url": "fuchsia-pkg://fuchsia.com/netstack/stable#meta/netstack.cm",
5390                    },
5391                ],
5392                "collections": [
5393                    {
5394                        "name": "modular",
5395                        "durability": "transient",
5396                    },
5397                ],
5398                "capabilities": [
5399                    {
5400                        "directory": "blobfs",
5401                        "path": "/volumes/blobfs",
5402                        "rights": [ "r*" ],
5403                    },
5404                    {
5405                        "runner": "myrunner",
5406                        "path": "/runner",
5407                    },
5408                    {
5409                        "protocol": "fuchsia.serve.service",
5410                    }
5411                ],
5412                "facets": {
5413                    "author": "Fuchsia",
5414                    "year": "2018",
5415                },
5416                "environments": [
5417                    {
5418                        "name": "myenv",
5419                        "extends": "realm",
5420                        "debug": [
5421                            {
5422                                "protocol": "fuchsia.serve.service",
5423                                "from": "self",
5424                                "as": "my-service",
5425                            },
5426                            {
5427                                "protocol": "fuchsia.logger.LegacyLog",
5428                                "from": "#logger",
5429                            }
5430                        ]
5431                    }
5432                ],
5433            }),
5434            output = fdecl::Component {
5435                program: Some(fdecl::Program {
5436                    runner: Some("elf".to_string()),
5437                    info: Some(fdata::Dictionary {
5438                        entries: Some(vec![fdata::DictionaryEntry {
5439                            key: "binary".to_string(),
5440                            value: Some(Box::new(fdata::DictionaryValue::Str("bin/app".to_string()))),
5441                        }]),
5442                        ..Default::default()
5443                    }),
5444                    ..Default::default()
5445                }),
5446                uses: Some(vec![
5447                    fdecl::Use::Protocol (
5448                        fdecl::UseProtocol {
5449                            dependency_type: Some(fdecl::DependencyType::Strong),
5450                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5451                            source_name: Some("LegacyCoolFonts".to_string()),
5452                            target_path: Some("/svc/fuchsia.fonts.LegacyProvider".to_string()),
5453                            availability: Some(fdecl::Availability::Required),
5454                            ..Default::default()
5455                        }
5456                    ),
5457                    fdecl::Use::Protocol (
5458                        fdecl::UseProtocol {
5459                            dependency_type: Some(fdecl::DependencyType::Strong),
5460                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5461                            source_name: Some("ReallyGoodFonts".to_string()),
5462                            target_path: Some("/svc/ReallyGoodFonts".to_string()),
5463                            availability: Some(fdecl::Availability::Required),
5464                            ..Default::default()
5465                        }
5466                    ),
5467                    fdecl::Use::Protocol (
5468                        fdecl::UseProtocol {
5469                            dependency_type: Some(fdecl::DependencyType::Strong),
5470                            source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5471                            source_name: Some("IWouldNeverUseTheseFonts".to_string()),
5472                            target_path: Some("/svc/IWouldNeverUseTheseFonts".to_string()),
5473                            availability: Some(fdecl::Availability::Required),
5474                            ..Default::default()
5475                        }
5476                    ),
5477                    fdecl::Use::Protocol (
5478                        fdecl::UseProtocol {
5479                            dependency_type: Some(fdecl::DependencyType::Strong),
5480                            source: Some(fdecl::Ref::Debug(fdecl::DebugRef {})),
5481                            source_name: Some("DebugProtocol".to_string()),
5482                            target_path: Some("/svc/DebugProtocol".to_string()),
5483                            availability: Some(fdecl::Availability::Required),
5484                            ..Default::default()
5485                        }
5486                    ),
5487                ]),
5488                exposes: Some(vec![
5489                    fdecl::Expose::Directory (
5490                        fdecl::ExposeDirectory {
5491                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
5492                            source_name: Some("blobfs".to_string()),
5493                            target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5494                            target_name: Some("blobfs".to_string()),
5495                            rights: Some(
5496                                fio::Operations::CONNECT | fio::Operations::ENUMERATE |
5497                                fio::Operations::TRAVERSE | fio::Operations::READ_BYTES |
5498                                fio::Operations::GET_ATTRIBUTES
5499                            ),
5500                            subdir: None,
5501                            availability: Some(fdecl::Availability::Required),
5502                            ..Default::default()
5503                        }
5504                    ),
5505                ]),
5506                offers: Some(vec![
5507                    fdecl::Offer::Protocol (
5508                        fdecl::OfferProtocol {
5509                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5510                                name: "logger".to_string(),
5511                                collection: None,
5512                            })),
5513                            source_name: Some("fuchsia.logger.LegacyLog".to_string()),
5514                            target: Some(fdecl::Ref::Child(fdecl::ChildRef {
5515                                name: "netstack".to_string(),
5516                                collection: None,
5517                            })),
5518                            target_name: Some("fuchsia.logger.LegacyLog".to_string()),
5519                            dependency_type: Some(fdecl::DependencyType::Weak),
5520                            availability: Some(fdecl::Availability::Required),
5521                            ..Default::default()
5522                        }
5523                    ),
5524                    fdecl::Offer::Protocol (
5525                        fdecl::OfferProtocol {
5526                            source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5527                                name: "logger".to_string(),
5528                                collection: None,
5529                            })),
5530                            source_name: Some("fuchsia.logger.LegacyLog".to_string()),
5531                            target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
5532                                name: "modular".to_string(),
5533                            })),
5534                            target_name: Some("fuchsia.logger.LegacyLog".to_string()),
5535                            dependency_type: Some(fdecl::DependencyType::Weak),
5536                            availability: Some(fdecl::Availability::Required),
5537                            ..Default::default()
5538                        }
5539                    ),
5540                ]),
5541                capabilities: Some(vec![
5542                    fdecl::Capability::Directory (
5543                        fdecl::Directory {
5544                            name: Some("blobfs".to_string()),
5545                            source_path: Some("/volumes/blobfs".to_string()),
5546                            rights: Some(fio::Operations::CONNECT | fio::Operations::ENUMERATE |
5547                                fio::Operations::TRAVERSE | fio::Operations::READ_BYTES |
5548                                fio::Operations::GET_ATTRIBUTES
5549                            ),
5550                            ..Default::default()
5551                        }
5552                    ),
5553                    fdecl::Capability::Runner (
5554                        fdecl::Runner {
5555                            name: Some("myrunner".to_string()),
5556                            source_path: Some("/runner".to_string()),
5557                            ..Default::default()
5558                        }
5559                    ),
5560                    fdecl::Capability::Protocol(
5561                        fdecl::Protocol {
5562                            name : Some("fuchsia.serve.service".to_owned()),
5563                            source_path: Some("/svc/fuchsia.serve.service".to_owned()),
5564                            ..Default::default()
5565                        }
5566                    )
5567                ]),
5568                children: Some(vec![
5569                    fdecl::Child {
5570                        name: Some("logger".to_string()),
5571                        url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
5572                        startup: Some(fdecl::StartupMode::Lazy),
5573                        environment: None,
5574                        on_terminate: None,
5575                        ..Default::default()
5576                    },
5577                    fdecl::Child {
5578                        name: Some("netstack".to_string()),
5579                        url: Some("fuchsia-pkg://fuchsia.com/netstack/stable#meta/netstack.cm".to_string()),
5580                        startup: Some(fdecl::StartupMode::Lazy),
5581                        environment: None,
5582                        on_terminate: None,
5583                        ..Default::default()
5584                    },
5585                ]),
5586                collections: Some(vec![
5587                    fdecl::Collection {
5588                        name: Some("modular".to_string()),
5589                        durability: Some(fdecl::Durability::Transient),
5590                        environment: None,
5591                        allowed_offers: None,
5592                        ..Default::default()
5593                    }
5594                ]),
5595                environments: Some(vec![
5596                    fdecl::Environment {
5597                        name: Some("myenv".to_string()),
5598                        extends: Some(fdecl::EnvironmentExtends::Realm),
5599                        runners: None,
5600                        resolvers: None,
5601                        stop_timeout_ms: None,
5602                        debug_capabilities: Some(vec![
5603                            fdecl::DebugRegistration::Protocol( fdecl::DebugProtocolRegistration {
5604                                source_name: Some("fuchsia.serve.service".to_string()),
5605                                source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
5606                                target_name: Some("my-service".to_string()),
5607                                ..Default::default()
5608                            }),
5609                            fdecl::DebugRegistration::Protocol( fdecl::DebugProtocolRegistration {
5610                                source_name: Some("fuchsia.logger.LegacyLog".to_string()),
5611                                source: Some(fdecl::Ref::Child(fdecl::ChildRef {
5612                                    name: "logger".to_string(),
5613                                    collection: None,
5614                                })),
5615                                target_name: Some("fuchsia.logger.LegacyLog".to_string()),
5616                                ..Default::default()
5617                            }),
5618                        ]),
5619                        ..Default::default()
5620                    }
5621                ]),
5622                facets: Some(fdata::Dictionary {
5623                        entries: Some(vec![
5624                            fdata::DictionaryEntry {
5625                                key: "author".to_string(),
5626                                value: Some(Box::new(fdata::DictionaryValue::Str("Fuchsia".to_string()))),
5627                            },
5628                            fdata::DictionaryEntry {
5629                                key: "year".to_string(),
5630                                value: Some(Box::new(fdata::DictionaryValue::Str("2018".to_string()))),
5631                            },
5632                        ]),
5633                        ..Default::default()
5634                    }),
5635                ..Default::default()
5636            },
5637        },
5638    }
5639
5640    #[test]
5641    fn test_maybe_generate_specialization_from_all() {
5642        let offer = create_offer(
5643            "fuchsia.logger.LegacyLog",
5644            OneOrMany::One(OfferFromRef::Parent {}),
5645            OneOrMany::One(OfferToRef::All),
5646        );
5647
5648        let mut offer_set = vec![create_offer(
5649            "fuchsia.logger.LogSink",
5650            OneOrMany::One(OfferFromRef::Parent {}),
5651            OneOrMany::One(OfferToRef::All),
5652        )];
5653
5654        let result = maybe_generate_direct_offer_from_all(
5655            &offer,
5656            &offer_set,
5657            &Name::from_str("something").unwrap(),
5658        );
5659
5660        assert_matches!(&result[..], [Offer {protocol, from, to, ..}] => {
5661            assert_eq!(
5662                protocol,
5663                &Some(OneOrMany::One(Name::from_str("fuchsia.logger.LegacyLog").unwrap())),
5664            );
5665            assert_eq!(from, &OneOrMany::One(OfferFromRef::Parent {}));
5666            assert_eq!(
5667                to,
5668                &OneOrMany::One(OfferToRef::Named(Name::from_str("something").unwrap())),
5669            );
5670        });
5671
5672        offer_set.push(create_offer(
5673            "fuchsia.inspect.InspectSink",
5674            OneOrMany::One(OfferFromRef::Parent {}),
5675            OneOrMany::One(OfferToRef::Named(Name::from_str("something").unwrap())),
5676        ));
5677
5678        let result = maybe_generate_direct_offer_from_all(
5679            &offer,
5680            &offer_set,
5681            &Name::from_str("something").unwrap(),
5682        );
5683
5684        assert_matches!(&result[..], [Offer {protocol, from, to, ..}] => {
5685            assert_eq!(
5686                protocol,
5687                &Some(OneOrMany::One(Name::from_str("fuchsia.logger.LegacyLog").unwrap())),
5688            );
5689            assert_eq!(from, &OneOrMany::One(OfferFromRef::Parent {}));
5690            assert_eq!(
5691                to,
5692                &OneOrMany::One(OfferToRef::Named(Name::from_str("something").unwrap())),
5693            );
5694        });
5695
5696        offer_set.push(create_offer(
5697            "fuchsia.logger.LegacyLog",
5698            OneOrMany::One(OfferFromRef::Parent {}),
5699            OneOrMany::One(OfferToRef::Named(Name::from_str("something").unwrap())),
5700        ));
5701
5702        assert!(maybe_generate_direct_offer_from_all(
5703            &offer,
5704            &offer_set,
5705            &Name::from_str("something").unwrap()
5706        )
5707        .is_empty());
5708    }
5709
5710    #[test]
5711    fn test_expose_void_service_capability() {
5712        let input = must_parse_cml!({
5713            "expose": [
5714                {
5715                    "service": "fuchsia.foo.Bar",
5716                    "from": [ "#non_existent_child" ],
5717                    "source_availability": "unknown",
5718                },
5719            ],
5720        });
5721        let result = compile(&input, CompileOptions::default());
5722        assert_matches!(result, Ok(_));
5723    }
5724
5725    /// Different availabilities aggregated by several service expose declarations is an error.
5726    #[test]
5727    fn test_aggregated_capabilities_must_use_same_availability_expose() {
5728        // Same availability.
5729        let input = must_parse_cml!({
5730            "expose": [
5731                {
5732                    "service": "fuchsia.foo.Bar",
5733                    "from": [ "#a", "#b" ],
5734                    "availability": "optional",
5735                },
5736            ],
5737            "collections": [
5738                {
5739                    "name": "a",
5740                    "durability": "transient",
5741                },
5742                {
5743                    "name": "b",
5744                    "durability": "transient",
5745                },
5746            ],
5747        });
5748        let result = compile(&input, CompileOptions::default());
5749        assert_matches!(result, Ok(_));
5750
5751        // Different availability.
5752        let input = must_parse_cml!({
5753            "expose": [
5754                {
5755                    "service": "fuchsia.foo.Bar",
5756                    "from": [ "#a", "#non_existent" ],
5757                    "source_availability": "unknown",
5758                },
5759            ],
5760            "collections": [
5761                {
5762                    "name": "a",
5763                    "durability": "transient",
5764                },
5765            ],
5766        });
5767        let result = compile(&input, CompileOptions::default());
5768        assert_matches!(
5769            result,
5770            Err(Error::FidlValidator  { errs: ErrorList { errs } })
5771            if matches!(
5772                &errs[..],
5773                [
5774                    CmFidlError::DifferentAvailabilityInAggregation(AvailabilityList(availabilities)),
5775                ]
5776                if matches!(
5777                    &availabilities[..],
5778                    [ fdecl::Availability::Required, fdecl::Availability::Optional, ]
5779                )
5780            )
5781        );
5782    }
5783
5784    #[test]
5785    fn test_aggregated_capabilities_must_use_same_availability_offer() {
5786        // Same availability.
5787        let input = must_parse_cml!({
5788            "offer": [
5789                {
5790                    "service": "fuchsia.foo.Bar",
5791                    "from": [ "#a", "#b" ],
5792                    "to": "#c",
5793                    "availability": "optional",
5794                },
5795            ],
5796            "collections": [
5797                {
5798                    "name": "a",
5799                    "durability": "transient",
5800                },
5801                {
5802                    "name": "b",
5803                    "durability": "transient",
5804                },
5805            ],
5806            "children": [
5807                {
5808                    "name": "c",
5809                    "url": "fuchsia-pkg://fuchsia.com/c/c#meta/c.cm",
5810                },
5811            ],
5812        });
5813        let result = compile(&input, CompileOptions::default());
5814        assert_matches!(result, Ok(_));
5815
5816        // Different availability.
5817        let input = must_parse_cml!({
5818            "offer": [
5819                {
5820                    "service": "fuchsia.foo.Bar",
5821                    "from": [ "#a", "#non_existent" ],
5822                    "to": "#c",
5823                    "source_availability": "unknown",
5824                },
5825            ],
5826            "collections": [
5827                {
5828                    "name": "a",
5829                    "durability": "transient",
5830                },
5831            ],
5832            "children": [
5833                {
5834                    "name": "c",
5835                    "url": "fuchsia-pkg://fuchsia.com/c/c#meta/c.cm",
5836                },
5837            ],
5838        });
5839        let result = compile(&input, CompileOptions::default());
5840        assert_matches!(
5841            result,
5842            Err(Error::FidlValidator  { errs: ErrorList { errs } })
5843            if matches!(
5844                &errs[..],
5845                [
5846                    CmFidlError::DifferentAvailabilityInAggregation(AvailabilityList(availabilities)),
5847                ]
5848                if matches!(
5849                    &availabilities[..],
5850                    [ fdecl::Availability::Required, fdecl::Availability::Optional, ]
5851                )
5852            )
5853        );
5854    }
5855
5856    #[test]
5857    fn test_compile_offer_to_all_exact_duplicate_disallowed() {
5858        let input = must_parse_cml!({
5859            "children": [
5860                {
5861                    "name": "logger",
5862                    "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
5863                },
5864            ],
5865            "offer": [
5866                {
5867                    "protocol": "fuchsia.logger.LogSink",
5868                    "from": "parent",
5869                    "to": "all",
5870                },
5871                {
5872                    "protocol": "fuchsia.logger.LogSink",
5873                    "from": "parent",
5874                    "to": "all",
5875                },
5876            ],
5877        });
5878        assert_matches!(
5879            compile(&input, CompileOptions::default()),
5880            Err(Error::Validate { err, .. })
5881            if &err == "Protocol(s) [\"fuchsia.logger.LogSink\"] offered to \"all\" multiple times"
5882        );
5883    }
5884
5885    #[test]
5886    fn test_compile_use_config() {
5887        let input = must_parse_cml!({
5888            "use": [
5889                    {
5890                        "config": "fuchsia.config.Config",
5891                        "key" : "my_config",
5892                        "type": "bool",
5893                    }
5894            ],
5895        });
5896        let options = CompileOptions::new().config_package_path("fake.cvf");
5897        let actual = compile(&input, options).unwrap();
5898        let type_ = fdecl::ConfigType {
5899            layout: fdecl::ConfigTypeLayout::Bool,
5900            parameters: Some(vec![]),
5901            constraints: vec![],
5902        };
5903        assert_eq!(
5904            actual.uses.unwrap(),
5905            vec![fdecl::Use::Config(fdecl::UseConfiguration {
5906                source_name: Some("fuchsia.config.Config".to_string()),
5907                source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
5908                target_name: Some("my_config".to_string()),
5909                availability: Some(fdecl::Availability::Required),
5910                type_: Some(type_.clone()),
5911                ..Default::default()
5912            })]
5913        );
5914        assert_eq!(
5915            actual.config.unwrap().fields.unwrap(),
5916            [fdecl::ConfigField {
5917                key: Some("my_config".to_string()),
5918                type_: Some(type_),
5919                mutability: Some(fdecl::ConfigMutability::default()),
5920                ..Default::default()
5921            }]
5922            .to_vec(),
5923        );
5924    }
5925
5926    #[test]
5927    fn test_compile_use_config_optional_bad_type() {
5928        let input = must_parse_cml!({
5929            "use": [
5930                    {
5931                        "config": "fuchsia.config.Config",
5932                        "key" : "my_config",
5933                        "type": "bool",
5934                        "availability": "optional",
5935                    }
5936            ],
5937        "config": {
5938            "my_config": { "type": "int8"},
5939        }
5940        });
5941        let options = CompileOptions::new().config_package_path("fake.cvf");
5942        assert_matches!(
5943            compile(&input, options),
5944            Err(Error::Validate { err, .. })
5945            if &err == "Use and config block differ on type for key 'my_config'"
5946        );
5947    }
5948
5949    #[test]
5950    fn test_config_source_from_package() {
5951        let input = must_parse_cml!({
5952            "use": [
5953                    {
5954                        "config": "fuchsia.config.Config",
5955                        "key" : "my_config",
5956                        "type": "bool",
5957                        "availability": "optional",
5958                    }
5959            ],
5960        "config": {
5961            "my_config": { "type": "bool"},
5962        }
5963        });
5964        let options = CompileOptions::new().config_package_path("fake.cvf");
5965        let decl = compile(&input, options).unwrap();
5966        let config = decl.config.unwrap();
5967        assert_eq!(
5968            config.value_source,
5969            Some(fdecl::ConfigValueSource::PackagePath("fake.cvf".into()))
5970        );
5971    }
5972
5973    #[test]
5974    fn test_config_source_from_capabilities() {
5975        let input = must_parse_cml!({
5976            "use": [
5977                    {
5978                        "config": "fuchsia.config.Config",
5979                        "key" : "my_config",
5980                        "type": "bool",
5981                    }
5982            ],
5983        });
5984        let options = CompileOptions::new().config_package_path("fake.cvf");
5985        let decl = compile(&input, options).unwrap();
5986        let config = decl.config.unwrap();
5987        assert_eq!(
5988            config.value_source,
5989            Some(
5990                fdecl::ConfigValueSource::Capabilities(fdecl::ConfigSourceCapabilities::default())
5991            )
5992        );
5993    }
5994
5995    #[test]
5996    fn test_config_default() {
5997        let input = must_parse_cml!({
5998            "use": [
5999                    {
6000                        "config": "fuchsia.config.Config",
6001                        "key" : "my_config",
6002                        "type": "bool",
6003                        "availability": "optional",
6004                        "default": true
6005                    }
6006            ],
6007        });
6008        let options = CompileOptions::new().config_package_path("fake.cvf");
6009        let decl = compile(&input, options).unwrap();
6010        assert_matches!(
6011            decl.uses.as_ref().unwrap()[0],
6012            fdecl::Use::Config(fdecl::UseConfiguration {
6013                default: Some(fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Bool(true))),
6014                ..
6015            })
6016        );
6017    }
6018
6019    #[test]
6020    fn test_config_default_bad_type() {
6021        let input = must_parse_cml!({
6022            "use": [
6023                    {
6024                        "config": "fuchsia.config.Config",
6025                        "key" : "my_config",
6026                        "type": "bool",
6027                        "availability": "optional",
6028                        "default": 5
6029                    }
6030            ],
6031        });
6032        let options = CompileOptions::new().config_package_path("fake.cvf");
6033        assert_matches!(compile(&input, options), Err(Error::InvalidArgs(_)));
6034    }
6035
6036    #[test]
6037    fn test_compile_protocol_delivery_type() {
6038        let input = must_parse_cml!({
6039            "capabilities": [
6040                {
6041                    "protocol": "fuchsia.echo.Echo",
6042                    "delivery": "on_readable",
6043                }
6044            ],
6045        });
6046        let features = FeatureSet::from(vec![Feature::DeliveryType]);
6047        let options = CompileOptions::new().features(&features);
6048        let decl = compile(&input, options).unwrap();
6049        assert_matches!(
6050            decl.capabilities.as_ref().unwrap()[0],
6051            fdecl::Capability::Protocol(fdecl::Protocol {
6052                delivery: Some(fdecl::DeliveryType::OnReadable),
6053                ..
6054            })
6055        );
6056    }
6057
6058    #[test]
6059    fn test_compile_protocol_setting_delivery_type_requires_feature_flag() {
6060        let input = must_parse_cml!({
6061            "capabilities": [
6062                {
6063                    "protocol": "fuchsia.echo.Echo",
6064                    "delivery": "on_readable",
6065                }
6066            ],
6067        });
6068        assert_matches!(
6069            compile(&input, CompileOptions::new()),
6070            Err(Error::RestrictedFeature(feature))
6071            if feature == "delivery_type"
6072        );
6073    }
6074}