blob: 852f042487bb8e78a55cb1ac512e38d96ee974f5 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
crate::{
error::Error, AnyRef, AsClause, Availability, Capability, CapabilityClause, Child,
Collection, ConfigKey, ConfigNestedValueType, ConfigValueType, DebugRegistration, Document,
Environment, EnvironmentExtends, EnvironmentRef, EventScope, EventSubscriptionsClause,
Expose, ExposeFromRef, ExposeToRef, FromClause, Offer, OneOrMany, Path, PathClause,
Program, ResolverRegistration, RightsClause, RunnerRegistration, SourceAvailability, Use,
UseFromRef,
},
cm_types::{self as cm, Name},
fidl_fuchsia_component_decl as fdecl, fidl_fuchsia_data as fdata, fidl_fuchsia_io as fio,
serde_json::{Map, Value},
sha2::{Digest, Sha256},
std::collections::{BTreeMap, HashSet},
std::convert::{Into, TryInto},
};
/// Compiles the Document into a FIDL `Component`.
/// Note: This function ignores the `include` section of the document. It is
/// assumed that those entries were already processed.
pub fn compile(
document: &Document,
config_package_path: Option<&str>,
) -> Result<fdecl::Component, Error> {
let all_capability_names = document.all_capability_names();
let all_children = document.all_children_names().into_iter().collect();
let all_collections = document.all_collection_names().into_iter().collect();
Ok(fdecl::Component {
program: document.program.as_ref().map(|p| translate_program(p)).transpose()?,
uses: document
.r#use
.as_ref()
.map(|u| translate_use(u, &all_capability_names, &all_children, &all_collections))
.transpose()?,
exposes: document
.expose
.as_ref()
.map(|e| translate_expose(e, &all_capability_names, &all_collections, &all_children))
.transpose()?,
offers: document
.offer
.as_ref()
.map(|offer| {
translate_offer(offer, &all_capability_names, &all_children, &all_collections)
})
.transpose()?,
capabilities: document
.capabilities
.as_ref()
.map(|c| translate_capabilities(c, false))
.transpose()?,
children: document.children.as_ref().map(translate_children).transpose()?,
collections: document.collections.as_ref().map(translate_collections).transpose()?,
environments: document
.environments
.as_ref()
.map(|env| translate_environments(env, &all_capability_names))
.transpose()?,
facets: document.facets.clone().map(dictionary_from_nested_map).transpose()?,
config: document
.config
.as_ref()
.map(|c| {
if let Some(p) = config_package_path {
Ok(translate_config(c, p))
} else {
Err(Error::invalid_args(
"can't translate config: no package path for value file",
))
}
})
.transpose()?,
..fdecl::Component::EMPTY
})
}
// Converts a Map<String, serde_json::Value> to a fuchsia Dictionary.
fn dictionary_from_map(in_obj: Map<String, Value>) -> Result<fdata::Dictionary, Error> {
let mut entries = vec![];
for (key, v) in in_obj {
let value = value_to_dictionary_value(v)?;
entries.push(fdata::DictionaryEntry { key, value });
}
Ok(fdata::Dictionary { entries: Some(entries), ..fdata::Dictionary::EMPTY })
}
// Converts a serde_json::Value into a fuchsia DictionaryValue.
fn value_to_dictionary_value(value: Value) -> Result<Option<Box<fdata::DictionaryValue>>, Error> {
match value {
Value::Null => Ok(None),
Value::String(s) => Ok(Some(Box::new(fdata::DictionaryValue::Str(s.clone())))),
Value::Array(arr) => {
if arr.iter().all(Value::is_string) {
let strs =
arr.into_iter().map(|v| v.as_str().unwrap().to_owned()).collect::<Vec<_>>();
Ok(Some(Box::new(fdata::DictionaryValue::StrVec(strs))))
} else if arr.iter().all(Value::is_object) {
let objs = arr
.into_iter()
.map(|v| v.as_object().unwrap().clone())
.map(|v| dictionary_from_nested_map(v))
.collect::<Result<Vec<_>, _>>()?;
Ok(Some(Box::new(fdata::DictionaryValue::ObjVec(objs))))
} else {
Err(Error::validate(
"Values of an array must either exclusively strings or exclusively objects",
))
}
}
other => Err(Error::validate(format!(
"Value must be string, list of strings, or list of objects: {:?}",
other
))),
}
}
/// Converts a [`serde_json::Map<String, serde_json::Value>`] to a [`fuchsia.data.Dictionary`].
///
/// The JSON object is converted as follows:
///
/// * Convert all non-string and string values into DictionaryValue::str.
/// * Flatten nested objects into top-level keys delimited by ".".
/// * Convert array of discrete values into array of DictionaryValue::str_vec.
/// * Convert array of objects into array of DictionaryValue::obj_vec.
///
/// Values may be null, strings, arrays of strings, arrays of objects, or objects.
///
/// # Example
///
/// ```json
/// {
/// "binary": "bin/app",
/// "lifecycle": {
/// "stop_event": "notify",
/// "nested": {
/// "foo": "bar"
/// }
/// }
/// }
/// ```
///
/// is flattened to:
///
/// ```json
/// {
/// "binary": "bin/app",
/// "lifecycle.stop_event": "notify",
/// "lifecycle.nested.foo": "bar"
/// }
/// ```
fn dictionary_from_nested_map(map: Map<String, Value>) -> Result<fdata::Dictionary, Error> {
fn key_value_to_entries(
key: String,
value: Value,
) -> Result<Vec<fdata::DictionaryEntry>, Error> {
if let Value::Object(map) = value {
let entries = map
.into_iter()
.map(|(k, v)| key_value_to_entries([key.clone(), ".".to_string(), k].concat(), v))
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.flatten()
.collect();
return Ok(entries);
}
let entry_value = value_to_dictionary_value(value)?;
Ok(vec![fdata::DictionaryEntry { key, value: entry_value }])
}
let entries = map
.into_iter()
.map(|(k, v)| key_value_to_entries(k, v))
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.flatten()
.collect();
Ok(fdata::Dictionary { entries: Some(entries), ..fdata::Dictionary::EMPTY })
}
/// Translates a [`Program`] to a [`fuchsa.sys2.Program`].
fn translate_program(program: &Program) -> Result<fdecl::Program, Error> {
Ok(fdecl::Program {
runner: program.runner.as_ref().map(|r| r.to_string()),
info: Some(dictionary_from_nested_map(program.info.clone())?),
..fdecl::Program::EMPTY
})
}
/// `use` rules consume a single capability from one source (parent|framework).
fn translate_use(
use_in: &Vec<Use>,
all_capability_names: &HashSet<Name>,
all_children: &HashSet<&Name>,
all_collections: &HashSet<&Name>,
) -> Result<Vec<fdecl::Use>, Error> {
let mut out_uses = vec![];
for use_ in use_in {
if let Some(n) = &use_.service {
let source = extract_use_source(use_, all_capability_names, all_children)?;
let target_paths =
all_target_use_paths(use_, use_).ok_or_else(|| Error::internal("no capability"))?;
let source_names = n.to_vec();
let availability = extract_use_availability(use_)?;
for (source_name, target_path) in source_names.into_iter().zip(target_paths.into_iter())
{
out_uses.push(fdecl::Use::Service(fdecl::UseService {
source: Some(clone_ref(&source)?),
source_name: Some(source_name.clone().into()),
target_path: Some(target_path.into()),
dependency_type: Some(
use_.dependency.clone().unwrap_or(cm::DependencyType::Strong).into(),
),
availability: Some(availability),
..fdecl::UseService::EMPTY
}));
}
} else if let Some(n) = &use_.protocol {
let source = extract_use_source(use_, all_capability_names, all_children)?;
let target_paths =
all_target_use_paths(use_, use_).ok_or_else(|| Error::internal("no capability"))?;
let source_names = n.to_vec();
let availability = extract_use_availability(use_)?;
for (source_name, target_path) in source_names.into_iter().zip(target_paths.into_iter())
{
out_uses.push(fdecl::Use::Protocol(fdecl::UseProtocol {
source: Some(clone_ref(&source)?),
source_name: Some(source_name.clone().into()),
target_path: Some(target_path.into()),
dependency_type: Some(
use_.dependency.clone().unwrap_or(cm::DependencyType::Strong).into(),
),
availability: Some(availability),
..fdecl::UseProtocol::EMPTY
}));
}
} else if let Some(n) = &use_.directory {
let source = extract_use_source(use_, all_capability_names, all_children)?;
let target_path = one_target_use_path(use_, use_)?;
let rights = extract_required_rights(use_, "use")?;
let subdir = extract_use_subdir(use_);
let availability = extract_use_availability(use_)?;
out_uses.push(fdecl::Use::Directory(fdecl::UseDirectory {
source: Some(source),
source_name: Some(n.clone().into()),
target_path: Some(target_path.into()),
rights: Some(rights),
subdir: subdir.map(|s| s.into()),
dependency_type: Some(
use_.dependency.clone().unwrap_or(cm::DependencyType::Strong).into(),
),
availability: Some(availability),
..fdecl::UseDirectory::EMPTY
}));
} else if let Some(n) = &use_.storage {
let target_path = one_target_use_path(use_, use_)?;
let availability = extract_use_availability(use_)?;
out_uses.push(fdecl::Use::Storage(fdecl::UseStorage {
source_name: Some(n.clone().into()),
target_path: Some(target_path.into()),
availability: Some(availability),
..fdecl::UseStorage::EMPTY
}));
} else if let Some(n) = &use_.event {
let source = extract_use_event_source(use_)?;
let target_names = all_target_capability_names(use_, use_)
.ok_or_else(|| Error::internal("no capability"))?;
let source_names = n.to_vec();
let availability = extract_use_availability(use_)?;
for target_name in target_names {
// When multiple source names are provided, there is no way to alias each one, so
// source_name == target_name,
// When one source name is provided, source_name may be aliased to a different
// target_name, so we use source_names[0] to derive the source_name.
let source_name = if source_names.len() == 1 {
source_names[0].clone()
} else {
target_name.clone()
};
out_uses.push(fdecl::Use::Event(fdecl::UseEvent {
source: Some(clone_ref(&source)?),
source_name: Some(source_name.into()),
target_name: Some(target_name.into()),
// We have already validated that none will be present if we were using many
// events.
filter: match use_.filter.clone() {
Some(dict) => Some(dictionary_from_map(dict)?),
None => None,
},
dependency_type: Some(
use_.dependency.clone().unwrap_or(cm::DependencyType::Strong).into(),
),
availability: Some(availability),
..fdecl::UseEvent::EMPTY
}));
}
} else if let Some(name) = &use_.event_stream_deprecated {
let opt_subscriptions = use_.event_subscriptions();
let availability = extract_use_availability(use_)?;
out_uses.push(fdecl::Use::EventStreamDeprecated(fdecl::UseEventStreamDeprecated {
name: Some(name.to_string()),
availability: Some(availability),
subscriptions: opt_subscriptions.map(|subscriptions| {
subscriptions
.iter()
.flat_map(|subscription| {
subscription.event.iter().map(move |event| fdecl::EventSubscription {
event_name: Some(event.to_string()),
..fdecl::EventSubscription::EMPTY
})
})
.collect()
}),
..fdecl::UseEventStreamDeprecated::EMPTY
}));
} else if let Some(names) = &use_.event_stream {
let source_names: Vec<String> =
annotate_type::<Vec<cm_types::Name>>(names.clone().into())
.iter()
.map(|name| name.to_string())
.collect();
let availability = extract_use_availability(use_)?;
for name in source_names {
let scopes = match use_.scope.clone() {
Some(value) => Some(annotate_type::<Vec<EventScope>>(value.into())),
None => None,
};
let internal_error = format!("Internal error in all_target_use_paths when translating an EventStream. Please file a bug.");
out_uses.push(fdecl::Use::EventStream(fdecl::UseEventStream {
source_name: Some(name),
scope: match scopes {
Some(values) => {
let mut output = vec![];
for value in &values {
output.push(translate_child_or_collection_ref(
value.into(),
&all_children,
&all_collections,
)?);
}
Some(output)
}
None => None,
},
source: Some(extract_use_source(use_, all_capability_names, all_children)?),
target_path: Some(
annotate_type::<Vec<cm_types::Path>>(
all_target_use_paths(use_, use_)
.ok_or_else(|| Error::internal(internal_error.clone()))?
.into(),
)
.iter()
.next()
.ok_or_else(|| Error::internal(internal_error.clone()))?
.as_str()
.to_string(),
),
availability: Some(availability),
..fdecl::UseEventStream::EMPTY
}));
}
} else {
return Err(Error::internal(format!("no capability in use declaration")));
};
}
Ok(out_uses)
}
/// `expose` rules route a single capability from one or more sources (self|framework|#<child>) to
/// one or more targets (parent|framework).
fn translate_expose(
expose_in: &Vec<Expose>,
all_capability_names: &HashSet<Name>,
all_collections: &HashSet<&Name>,
all_children: &HashSet<&Name>,
) -> Result<Vec<fdecl::Expose>, Error> {
let mut out_exposes = vec![];
for expose in expose_in.iter() {
let target = extract_expose_target(expose)?;
if let Some(source_names) = expose.service() {
let sources = extract_all_expose_sources(expose, Some(all_collections))?;
let target_names = all_target_capability_names(expose, expose)
.ok_or_else(|| Error::internal("no capability"))?;
for (source_name, target_name) in source_names.into_iter().zip(target_names.into_iter())
{
for source in &sources {
out_exposes.push(fdecl::Expose::Service(fdecl::ExposeService {
source: Some(clone_ref(source)?),
source_name: Some(source_name.clone().into()),
target_name: Some(target_name.clone().into()),
target: Some(clone_ref(&target)?),
..fdecl::ExposeService::EMPTY
}))
}
}
} else if let Some(n) = expose.protocol() {
let source = extract_single_expose_source(expose, Some(all_capability_names))?;
let source_names = n.to_vec();
let target_names = all_target_capability_names(expose, expose)
.ok_or_else(|| Error::internal("no capability"))?;
for (source_name, target_name) in source_names.into_iter().zip(target_names.into_iter())
{
out_exposes.push(fdecl::Expose::Protocol(fdecl::ExposeProtocol {
source: Some(clone_ref(&source)?),
source_name: Some(source_name.clone().into()),
target_name: Some(target_name.into()),
target: Some(clone_ref(&target)?),
..fdecl::ExposeProtocol::EMPTY
}))
}
} else if let Some(n) = expose.directory() {
let source = extract_single_expose_source(expose, None)?;
let source_names = n.to_vec();
let target_names = all_target_capability_names(expose, expose)
.ok_or_else(|| Error::internal("no capability"))?;
let rights = extract_expose_rights(expose)?;
let subdir = extract_expose_subdir(expose);
for (source_name, target_name) in source_names.into_iter().zip(target_names.into_iter())
{
out_exposes.push(fdecl::Expose::Directory(fdecl::ExposeDirectory {
source: Some(clone_ref(&source)?),
source_name: Some(source_name.clone().into()),
target_name: Some(target_name.into()),
target: Some(clone_ref(&target)?),
rights,
subdir: subdir.as_ref().map(|s| s.clone().into()),
..fdecl::ExposeDirectory::EMPTY
}))
}
} else if let Some(n) = expose.runner() {
let source = extract_single_expose_source(expose, None)?;
let source_names = n.to_vec();
let target_names = all_target_capability_names(expose, expose)
.ok_or_else(|| Error::internal("no capability"))?;
for (source_name, target_name) in source_names.into_iter().zip(target_names.into_iter())
{
out_exposes.push(fdecl::Expose::Runner(fdecl::ExposeRunner {
source: Some(clone_ref(&source)?),
source_name: Some(source_name.clone().into()),
target: Some(clone_ref(&target)?),
target_name: Some(target_name.into()),
..fdecl::ExposeRunner::EMPTY
}))
}
} else if let Some(n) = expose.resolver() {
let source = extract_single_expose_source(expose, None)?;
let source_names = n.to_vec();
let target_names = all_target_capability_names(expose, expose)
.ok_or_else(|| Error::internal("no capability"))?;
for (source_name, target_name) in source_names.into_iter().zip(target_names.into_iter())
{
out_exposes.push(fdecl::Expose::Resolver(fdecl::ExposeResolver {
source: Some(clone_ref(&source)?),
source_name: Some(source_name.clone().into()),
target: Some(clone_ref(&target)?),
target_name: Some(target_name.into()),
..fdecl::ExposeResolver::EMPTY
}))
}
} else if let Some(n) = expose.event_stream() {
let source = extract_single_expose_source(expose, None)?;
let source_names = n.to_vec();
let target_names = all_target_capability_names(expose, expose)
.ok_or_else(|| Error::internal("no capability"))?;
for (source_name, target_name) in source_names.into_iter().zip(target_names.into_iter())
{
let scopes = match expose.scope.clone() {
Some(value) => Some(annotate_type::<Vec<EventScope>>(value.into())),
None => None,
};
out_exposes.push(fdecl::Expose::EventStream(fdecl::ExposeEventStream {
source: Some(clone_ref(&source)?),
source_name: Some(source_name.clone().into()),
target: Some(clone_ref(&target)?),
target_name: Some(target_name.into()),
scope: match scopes {
Some(values) => {
let mut output = vec![];
for value in &values {
output.push(translate_child_or_collection_ref(
value.into(),
&all_children,
&all_collections,
)?);
}
Some(output)
}
None => None,
},
..fdecl::ExposeEventStream::EMPTY
}))
}
} else {
return Err(Error::internal(format!("expose: must specify a known capability")));
}
}
Ok(out_exposes)
}
impl<T> Into<Vec<T>> for OneOrMany<T> {
fn into(self) -> Vec<T> {
match self {
OneOrMany::One(one) => vec![one],
OneOrMany::Many(many) => many,
}
}
}
// Allows the above Into to work by annotating the type.
fn annotate_type<T>(val: T) -> T {
val
}
fn derive_source_and_availability(
availability: Option<&Availability>,
source: fdecl::Ref,
source_availability: Option<&SourceAvailability>,
all_capability_names: &HashSet<Name>,
all_children: &HashSet<&Name>,
all_collections: &HashSet<&Name>,
) -> (fdecl::Ref, fdecl::Availability) {
let availability = availability.map(|a| match a {
Availability::Required => fdecl::Availability::Required,
Availability::Optional => fdecl::Availability::Optional,
Availability::SameAsTarget => fdecl::Availability::SameAsTarget,
});
if source_availability != Some(&SourceAvailability::Unknown) {
return (source, availability.unwrap_or(fdecl::Availability::Required));
}
match &source {
fdecl::Ref::Child(fdecl::ChildRef { name, .. })
if !all_children.contains(&Name::new(name.clone()).unwrap()) =>
{
(
fdecl::Ref::VoidType(fdecl::VoidRef {}),
availability.unwrap_or(fdecl::Availability::Optional),
)
}
fdecl::Ref::Collection(fdecl::CollectionRef { name, .. })
if !all_collections.contains(&Name::new(name.clone()).unwrap()) =>
{
(
fdecl::Ref::VoidType(fdecl::VoidRef {}),
availability.unwrap_or(fdecl::Availability::Optional),
)
}
fdecl::Ref::Capability(fdecl::CapabilityRef { name, .. })
if !all_capability_names.contains(&Name::new(name.clone()).unwrap()) =>
{
(
fdecl::Ref::VoidType(fdecl::VoidRef {}),
availability.unwrap_or(fdecl::Availability::Optional),
)
}
_ => (source, availability.unwrap_or(fdecl::Availability::Required)),
}
}
/// `offer` rules route multiple capabilities from multiple sources to multiple targets.
fn translate_offer(
offer_in: &Vec<Offer>,
all_capability_names: &HashSet<Name>,
all_children: &HashSet<&Name>,
all_collections: &HashSet<&Name>,
) -> Result<Vec<fdecl::Offer>, Error> {
let mut out_offers = vec![];
for offer in offer_in.iter() {
if let Some(n) = offer.service() {
let entries = extract_offer_sources_and_targets(
offer,
n,
all_capability_names,
all_children,
all_collections,
)?;
for (source, source_name, target, target_name) in entries {
let (source, availability) = derive_source_and_availability(
offer.availability.as_ref(),
source,
offer.source_availability.as_ref(),
all_capability_names,
all_children,
all_collections,
);
out_offers.push(fdecl::Offer::Service(fdecl::OfferService {
source: Some(source),
source_name: Some(source_name.into()),
target: Some(target),
target_name: Some(target_name.into()),
availability: Some(availability),
..fdecl::OfferService::EMPTY
}));
}
} else if let Some(n) = offer.protocol() {
let entries = extract_offer_sources_and_targets(
offer,
n,
all_capability_names,
all_children,
all_collections,
)?;
for (source, source_name, target, target_name) in entries {
let (source, availability) = derive_source_and_availability(
offer.availability.as_ref(),
source,
offer.source_availability.as_ref(),
all_capability_names,
all_children,
all_collections,
);
out_offers.push(fdecl::Offer::Protocol(fdecl::OfferProtocol {
source: Some(source),
source_name: Some(source_name.into()),
target: Some(target),
target_name: Some(target_name.into()),
dependency_type: Some(
offer.dependency.clone().unwrap_or(cm::DependencyType::Strong).into(),
),
availability: Some(availability),
..fdecl::OfferProtocol::EMPTY
}));
}
} else if let Some(n) = offer.directory() {
let entries = extract_offer_sources_and_targets(
offer,
n,
all_capability_names,
all_children,
all_collections,
)?;
for (source, source_name, target, target_name) in entries {
let (source, availability) = derive_source_and_availability(
offer.availability.as_ref(),
source,
offer.source_availability.as_ref(),
all_capability_names,
all_children,
all_collections,
);
out_offers.push(fdecl::Offer::Directory(fdecl::OfferDirectory {
source: Some(source),
source_name: Some(source_name.into()),
target: Some(target),
target_name: Some(target_name.into()),
rights: extract_offer_rights(offer)?,
subdir: extract_offer_subdir(offer).map(|s| s.into()),
dependency_type: Some(
offer.dependency.clone().unwrap_or(cm::DependencyType::Strong).into(),
),
availability: Some(availability),
..fdecl::OfferDirectory::EMPTY
}));
}
} else if let Some(n) = offer.storage() {
let entries = extract_offer_sources_and_targets(
offer,
n,
all_capability_names,
all_children,
all_collections,
)?;
for (source, source_name, target, target_name) in entries {
let (source, availability) = derive_source_and_availability(
offer.availability.as_ref(),
source,
offer.source_availability.as_ref(),
all_capability_names,
all_children,
all_collections,
);
out_offers.push(fdecl::Offer::Storage(fdecl::OfferStorage {
source: Some(source),
source_name: Some(source_name.into()),
target: Some(target),
target_name: Some(target_name.into()),
availability: Some(availability),
..fdecl::OfferStorage::EMPTY
}));
}
} else if let Some(n) = offer.runner() {
let entries = extract_offer_sources_and_targets(
offer,
n,
all_capability_names,
all_children,
all_collections,
)?;
for (source, source_name, target, target_name) in entries {
out_offers.push(fdecl::Offer::Runner(fdecl::OfferRunner {
source: Some(source),
source_name: Some(source_name.into()),
target: Some(target),
target_name: Some(target_name.into()),
..fdecl::OfferRunner::EMPTY
}));
}
} else if let Some(n) = offer.resolver() {
let entries = extract_offer_sources_and_targets(
offer,
n,
all_capability_names,
all_children,
all_collections,
)?;
for (source, source_name, target, target_name) in entries {
out_offers.push(fdecl::Offer::Resolver(fdecl::OfferResolver {
source: Some(source),
source_name: Some(source_name.into()),
target: Some(target),
target_name: Some(target_name.into()),
..fdecl::OfferResolver::EMPTY
}));
}
} else if let Some(n) = offer.event() {
let entries = extract_offer_sources_and_targets(
offer,
n,
all_capability_names,
all_children,
all_collections,
)?;
for (source, source_name, target, target_name) in entries {
let (source, availability) = derive_source_and_availability(
offer.availability.as_ref(),
source,
offer.source_availability.as_ref(),
all_capability_names,
all_children,
all_collections,
);
out_offers.push(fdecl::Offer::Event(fdecl::OfferEvent {
source: Some(source),
source_name: Some(source_name.into()),
target: Some(target),
target_name: Some(target_name.into()),
// We have already validated that none will be present if we were using many
// events.
filter: match &offer.filter {
Some(dict) => Some(dictionary_from_map(dict.clone())?),
None => None,
},
availability: Some(availability),
..fdecl::OfferEvent::EMPTY
}));
}
} else if let Some(n) = offer.event_stream() {
let entries = extract_offer_sources_and_targets(
offer,
n,
all_capability_names,
all_children,
all_collections,
)?;
for (source, source_name, target, target_name) in entries {
let (source, availability) = derive_source_and_availability(
offer.availability.as_ref(),
source,
offer.source_availability.as_ref(),
all_capability_names,
all_children,
all_collections,
);
let scopes = match offer.scope.clone() {
Some(value) => Some(annotate_type::<Vec<EventScope>>(value.into())),
None => None,
};
out_offers.push(fdecl::Offer::EventStream(fdecl::OfferEventStream {
source: Some(source),
source_name: Some(source_name.into()),
target: Some(target),
target_name: Some(target_name.into()),
// We have already validated that none will be present if we were using many
// events.
filter: match offer.filter.clone() {
Some(dict) => Some(dictionary_from_map(dict)?),
None => None,
},
scope: match scopes {
Some(values) => {
let mut output = vec![];
for value in &values {
output.push(translate_child_or_collection_ref(
value.into(),
&all_children,
&all_collections,
)?);
}
Some(output)
}
None => None,
},
availability: Some(availability),
..fdecl::OfferEventStream::EMPTY
}));
}
} else {
return Err(Error::internal(format!("no capability")));
}
}
Ok(out_offers)
}
fn translate_children(children_in: &Vec<Child>) -> Result<Vec<fdecl::Child>, Error> {
let mut out_children = vec![];
for child in children_in.iter() {
out_children.push(fdecl::Child {
name: Some(child.name.clone().into()),
url: Some(child.url.clone().into()),
startup: Some(child.startup.clone().into()),
environment: extract_environment_ref(child.environment.as_ref()).map(|e| e.into()),
on_terminate: child.on_terminate.as_ref().map(|r| r.clone().into()),
..fdecl::Child::EMPTY
});
}
Ok(out_children)
}
fn translate_collections(
collections_in: &Vec<Collection>,
) -> Result<Vec<fdecl::Collection>, Error> {
let mut out_collections = vec![];
for collection in collections_in.iter() {
out_collections.push(fdecl::Collection {
name: Some(collection.name.clone().into()),
durability: Some(collection.durability.clone().into()),
environment: extract_environment_ref(collection.environment.as_ref()).map(|e| e.into()),
allowed_offers: collection.allowed_offers.clone().map(|a| a.into()),
allow_long_names: collection.allow_long_names.clone(),
persistent_storage: collection.persistent_storage.clone(),
..fdecl::Collection::EMPTY
});
}
Ok(out_collections)
}
/// Translates a nested value type to a [`fuchsia.config.decl.ConfigType`]
fn translate_nested_value_type(nested_type: &ConfigNestedValueType) -> fdecl::ConfigType {
let layout = match nested_type {
ConfigNestedValueType::Bool => fdecl::ConfigTypeLayout::Bool,
ConfigNestedValueType::Uint8 => fdecl::ConfigTypeLayout::Uint8,
ConfigNestedValueType::Uint16 => fdecl::ConfigTypeLayout::Uint16,
ConfigNestedValueType::Uint32 => fdecl::ConfigTypeLayout::Uint32,
ConfigNestedValueType::Uint64 => fdecl::ConfigTypeLayout::Uint64,
ConfigNestedValueType::Int8 => fdecl::ConfigTypeLayout::Int8,
ConfigNestedValueType::Int16 => fdecl::ConfigTypeLayout::Int16,
ConfigNestedValueType::Int32 => fdecl::ConfigTypeLayout::Int32,
ConfigNestedValueType::Int64 => fdecl::ConfigTypeLayout::Int64,
ConfigNestedValueType::String { .. } => fdecl::ConfigTypeLayout::String,
};
let constraints = match nested_type {
ConfigNestedValueType::String { max_size } => {
vec![fdecl::LayoutConstraint::MaxSize(max_size.get())]
}
_ => vec![],
};
fdecl::ConfigType {
layout,
constraints,
// This optional is not necessary, but without it,
// FIDL compilation complains because of a possible include-cycle.
// Bug: http://fxbug.dev/66350
parameters: Some(vec![]),
}
}
/// Translates a value type to a [`fuchsia.sys2.ConfigType`]
fn translate_value_type(value_type: &ConfigValueType) -> fdecl::ConfigType {
let layout = match value_type {
ConfigValueType::Bool => fdecl::ConfigTypeLayout::Bool,
ConfigValueType::Uint8 => fdecl::ConfigTypeLayout::Uint8,
ConfigValueType::Uint16 => fdecl::ConfigTypeLayout::Uint16,
ConfigValueType::Uint32 => fdecl::ConfigTypeLayout::Uint32,
ConfigValueType::Uint64 => fdecl::ConfigTypeLayout::Uint64,
ConfigValueType::Int8 => fdecl::ConfigTypeLayout::Int8,
ConfigValueType::Int16 => fdecl::ConfigTypeLayout::Int16,
ConfigValueType::Int32 => fdecl::ConfigTypeLayout::Int32,
ConfigValueType::Int64 => fdecl::ConfigTypeLayout::Int64,
ConfigValueType::String { .. } => fdecl::ConfigTypeLayout::String,
ConfigValueType::Vector { .. } => fdecl::ConfigTypeLayout::Vector,
};
let (constraints, parameters) = match value_type {
ConfigValueType::String { max_size } => {
(vec![fdecl::LayoutConstraint::MaxSize(max_size.get())], vec![])
}
ConfigValueType::Vector { max_count, element } => {
let nested_type = translate_nested_value_type(element);
(
vec![fdecl::LayoutConstraint::MaxSize(max_count.get())],
vec![fdecl::LayoutParameter::NestedType(nested_type)],
)
}
_ => (vec![], vec![]),
};
fdecl::ConfigType {
layout,
constraints,
// This optional is not necessary, but without it,
// FIDL compilation complains because of a possible include-cycle.
// Bug: http://fxbug.dev/66350
parameters: Some(parameters),
}
}
/// Translates a map of [`String`] -> [`ConfigValueType`] to a [`fuchsia.sys2.Config`]
fn translate_config(
fields: &BTreeMap<ConfigKey, ConfigValueType>,
package_path: &str,
) -> fdecl::ConfigSchema {
let mut fidl_fields = vec![];
// Compute a SHA-256 hash from each field
let mut hasher = Sha256::new();
for (key, value) in fields {
fidl_fields.push(fdecl::ConfigField {
key: Some(key.to_string()),
type_: Some(translate_value_type(value)),
..fdecl::ConfigField::EMPTY
});
hasher.update(key.as_str());
value.update_digest(&mut hasher);
}
let hash = hasher.finalize();
let checksum = fdecl::ConfigChecksum::Sha256(*hash.as_ref());
fdecl::ConfigSchema {
fields: Some(fidl_fields),
checksum: Some(checksum),
// for now we only support ELF components that look up config by package path
value_source: Some(fdecl::ConfigValueSource::PackagePath(package_path.to_owned())),
..fdecl::ConfigSchema::EMPTY
}
}
fn translate_environments(
envs_in: &Vec<Environment>,
all_capability_names: &HashSet<Name>,
) -> Result<Vec<fdecl::Environment>, Error> {
envs_in
.iter()
.map(|env| {
Ok(fdecl::Environment {
name: Some(env.name.clone().into()),
extends: match env.extends {
Some(EnvironmentExtends::Realm) => Some(fdecl::EnvironmentExtends::Realm),
Some(EnvironmentExtends::None) => Some(fdecl::EnvironmentExtends::None),
None => Some(fdecl::EnvironmentExtends::None),
},
runners: env
.runners
.as_ref()
.map(|runners| {
runners
.iter()
.map(translate_runner_registration)
.collect::<Result<Vec<_>, Error>>()
})
.transpose()?,
resolvers: env
.resolvers
.as_ref()
.map(|resolvers| {
resolvers
.iter()
.map(translate_resolver_registration)
.collect::<Result<Vec<_>, Error>>()
})
.transpose()?,
debug_capabilities: env
.debug
.as_ref()
.map(|debug_capabiltities| {
translate_debug_capabilities(debug_capabiltities, all_capability_names)
})
.transpose()?,
stop_timeout_ms: env.stop_timeout_ms.map(|s| s.0),
..fdecl::Environment::EMPTY
})
})
.collect()
}
fn translate_runner_registration(
reg: &RunnerRegistration,
) -> Result<fdecl::RunnerRegistration, Error> {
Ok(fdecl::RunnerRegistration {
source_name: Some(reg.runner.clone().into()),
source: Some(extract_single_offer_source(reg, None)?),
target_name: Some(reg.r#as.as_ref().unwrap_or(&reg.runner).clone().into()),
..fdecl::RunnerRegistration::EMPTY
})
}
fn translate_resolver_registration(
reg: &ResolverRegistration,
) -> Result<fdecl::ResolverRegistration, Error> {
Ok(fdecl::ResolverRegistration {
resolver: Some(reg.resolver.clone().into()),
source: Some(extract_single_offer_source(reg, None)?),
scheme: Some(
reg.scheme
.as_str()
.parse::<cm_types::UrlScheme>()
.map_err(|e| Error::internal(format!("invalid URL scheme: {}", e)))?
.into(),
),
..fdecl::ResolverRegistration::EMPTY
})
}
fn translate_debug_capabilities(
capabilities: &Vec<DebugRegistration>,
all_capability_names: &HashSet<Name>,
) -> Result<Vec<fdecl::DebugRegistration>, Error> {
let mut out_capabilities = vec![];
for capability in capabilities {
if let Some(n) = capability.protocol() {
let source = extract_single_offer_source(capability, Some(all_capability_names))?;
let targets = all_target_capability_names(capability, capability)
.ok_or_else(|| Error::internal("no capability"))?;
let source_names = n.to_vec();
for target_name in targets {
// When multiple source names are provided, there is no way to alias each one, so
// source_name == target_name.
// When one source name is provided, source_name may be aliased to a different
// target_name, so we source_names[0] to derive the source_name.
//
// TODO: This logic could be simplified to use iter::zip() if
// extract_all_targets_for_each_child returned separate vectors for targets and
// target_names instead of the cross product of them.
let source_name = if source_names.len() == 1 {
source_names[0].clone()
} else {
target_name.clone()
};
out_capabilities.push(fdecl::DebugRegistration::Protocol(
fdecl::DebugProtocolRegistration {
source: Some(clone_ref(&source)?),
source_name: Some(source_name.into()),
target_name: Some(target_name.into()),
..fdecl::DebugProtocolRegistration::EMPTY
},
));
}
}
}
Ok(out_capabilities)
}
fn extract_use_source(
in_obj: &Use,
all_capability_names: &HashSet<Name>,
all_children_names: &HashSet<&Name>,
) -> Result<fdecl::Ref, Error> {
match in_obj.from.as_ref() {
Some(UseFromRef::Parent) => Ok(fdecl::Ref::Parent(fdecl::ParentRef {})),
Some(UseFromRef::Framework) => Ok(fdecl::Ref::Framework(fdecl::FrameworkRef {})),
Some(UseFromRef::Debug) => Ok(fdecl::Ref::Debug(fdecl::DebugRef {})),
Some(UseFromRef::Self_) => Ok(fdecl::Ref::Self_(fdecl::SelfRef {})),
Some(UseFromRef::Named(name)) => {
if all_capability_names.contains(&name) {
Ok(fdecl::Ref::Capability(fdecl::CapabilityRef { name: name.clone().into() }))
} else if all_children_names.contains(&name) {
Ok(fdecl::Ref::Child(fdecl::ChildRef {
name: name.clone().into(),
collection: None,
}))
} else {
Err(Error::internal(format!(
"use source \"{:?}\" not supported for \"use from\"",
name
)))
}
}
None => Ok(fdecl::Ref::Parent(fdecl::ParentRef {})), // Default value.
}
}
fn extract_use_availability(in_obj: &Use) -> Result<fdecl::Availability, Error> {
match in_obj.availability.as_ref() {
Some(Availability::Required) | None => Ok(fdecl::Availability::Required),
Some(Availability::Optional) => Ok(fdecl::Availability::Optional),
_ => Err(Error::internal(
"availability \"same_as_target\" not supported for use declarations",
)),
}
}
// Since fdecl::Ref is not cloneable, write our own clone function.
fn clone_ref(ref_: &fdecl::Ref) -> Result<fdecl::Ref, Error> {
match ref_ {
fdecl::Ref::Parent(parent_ref) => Ok(fdecl::Ref::Parent(parent_ref.clone())),
fdecl::Ref::Self_(self_ref) => Ok(fdecl::Ref::Self_(self_ref.clone())),
fdecl::Ref::Child(child_ref) => Ok(fdecl::Ref::Child(child_ref.clone())),
fdecl::Ref::Collection(collection_ref) => {
Ok(fdecl::Ref::Collection(collection_ref.clone()))
}
fdecl::Ref::Framework(framework_ref) => Ok(fdecl::Ref::Framework(framework_ref.clone())),
fdecl::Ref::Capability(capability_ref) => {
Ok(fdecl::Ref::Capability(capability_ref.clone()))
}
fdecl::Ref::Debug(debug_ref) => Ok(fdecl::Ref::Debug(debug_ref.clone())),
_ => Err(Error::internal("Unknown fdecl::Ref found.")),
}
}
fn extract_use_event_source(in_obj: &Use) -> Result<fdecl::Ref, Error> {
match in_obj.from.as_ref() {
Some(UseFromRef::Parent) => Ok(fdecl::Ref::Parent(fdecl::ParentRef {})),
Some(UseFromRef::Framework) => Ok(fdecl::Ref::Framework(fdecl::FrameworkRef {})),
Some(UseFromRef::Named(name)) => {
Ok(fdecl::Ref::Capability(fdecl::CapabilityRef { name: name.clone().into() }))
}
Some(UseFromRef::Debug) => {
Err(Error::internal(format!("Debug source provided for \"use event\"")))
}
Some(UseFromRef::Self_) => {
Err(Error::internal(format!("Self source not supported for \"use event\"")))
}
None => Err(Error::internal(format!("No source \"from\" provided for \"use\""))),
}
}
fn extract_use_subdir(in_obj: &Use) -> Option<cm::RelativePath> {
in_obj.subdir.clone()
}
fn extract_expose_subdir(in_obj: &Expose) -> Option<cm::RelativePath> {
in_obj.subdir.clone()
}
fn extract_offer_subdir(in_obj: &Offer) -> Option<cm::RelativePath> {
in_obj.subdir.clone()
}
fn extract_expose_rights(in_obj: &Expose) -> Result<Option<fio::Operations>, Error> {
match in_obj.rights.as_ref() {
Some(rights_tokens) => {
let mut rights = Vec::new();
for token in rights_tokens.0.iter() {
rights.append(&mut token.expand())
}
if rights.is_empty() {
return Err(Error::missing_rights(
"Rights provided to expose are not well formed.",
));
}
let mut seen_rights = HashSet::with_capacity(rights.len());
let mut operations: fio::Operations = fio::Operations::empty();
for right in rights.iter() {
if seen_rights.contains(&right) {
return Err(Error::duplicate_rights(
"Rights provided to expose are not well formed.",
));
}
seen_rights.insert(right);
operations |= *right;
}
Ok(Some(operations))
}
// Unlike use rights, expose rights can take a None value
None => Ok(None),
}
}
fn expose_source_from_ref(
reference: &ExposeFromRef,
all_capability_names: Option<&HashSet<Name>>,
all_collections: Option<&HashSet<&Name>>,
) -> Result<fdecl::Ref, Error> {
match reference {
ExposeFromRef::Named(name) => {
if all_capability_names.is_some() && all_capability_names.unwrap().contains(&name) {
Ok(fdecl::Ref::Capability(fdecl::CapabilityRef { name: name.clone().into() }))
} else if all_collections.is_some() && all_collections.unwrap().contains(&name) {
Ok(fdecl::Ref::Collection(fdecl::CollectionRef { name: name.clone().into() }))
} else {
Ok(fdecl::Ref::Child(fdecl::ChildRef {
name: name.clone().into(),
collection: None,
}))
}
}
ExposeFromRef::Framework => Ok(fdecl::Ref::Framework(fdecl::FrameworkRef {})),
ExposeFromRef::Self_ => Ok(fdecl::Ref::Self_(fdecl::SelfRef {})),
}
}
fn extract_single_expose_source(
in_obj: &Expose,
all_capability_names: Option<&HashSet<Name>>,
) -> Result<fdecl::Ref, Error> {
match &in_obj.from {
OneOrMany::One(reference) => expose_source_from_ref(&reference, all_capability_names, None),
OneOrMany::Many(many) => {
return Err(Error::internal(format!(
"multiple unexpected \"from\" clauses for \"expose\": {:?}",
many
)))
}
}
}
fn extract_all_expose_sources(
in_obj: &Expose,
all_collections: Option<&HashSet<&Name>>,
) -> Result<Vec<fdecl::Ref>, Error> {
in_obj
.from
.to_vec()
.into_iter()
.map(|e| expose_source_from_ref(e, None, all_collections))
.collect()
}
fn extract_offer_rights(in_obj: &Offer) -> Result<Option<fio::Operations>, Error> {
match in_obj.rights.as_ref() {
Some(rights_tokens) => {
let mut rights = Vec::new();
for token in rights_tokens.0.iter() {
rights.append(&mut token.expand())
}
if rights.is_empty() {
return Err(Error::missing_rights("Rights provided to offer are not well formed."));
}
let mut seen_rights = HashSet::with_capacity(rights.len());
let mut operations: fio::Operations = fio::Operations::empty();
for right in rights.iter() {
if seen_rights.contains(&right) {
return Err(Error::duplicate_rights(
"Rights provided to offer are not well formed.",
));
}
seen_rights.insert(right);
operations |= *right;
}
Ok(Some(operations))
}
// Unlike use rights, offer rights can take a None value
None => Ok(None),
}
}
fn extract_single_offer_source<T>(
in_obj: &T,
all_capability_names: Option<&HashSet<Name>>,
) -> Result<fdecl::Ref, Error>
where
T: FromClause,
{
match in_obj.from_() {
OneOrMany::One(reference) => offer_source_from_ref(reference, all_capability_names, None),
many => {
return Err(Error::internal(format!(
"multiple unexpected \"from\" clauses for \"offer\": {}",
many
)))
}
}
}
fn extract_all_offer_sources<T: FromClause>(
in_obj: &T,
all_capability_names: &HashSet<Name>,
all_collections: &HashSet<&Name>,
) -> Result<Vec<fdecl::Ref>, Error> {
in_obj
.from_()
.to_vec()
.into_iter()
.map(|r| {
offer_source_from_ref(r.clone(), Some(all_capability_names), Some(all_collections))
})
.collect()
}
fn translate_child_or_collection_ref(
reference: AnyRef,
all_children: &HashSet<&Name>,
all_collections: &HashSet<&Name>,
) -> Result<fdecl::Ref, Error> {
match reference {
AnyRef::Named(name) if all_children.contains(name) => {
Ok(fdecl::Ref::Child(fdecl::ChildRef { name: name.clone().into(), collection: None }))
}
AnyRef::Named(name) if all_collections.contains(name) => {
Ok(fdecl::Ref::Collection(fdecl::CollectionRef { name: name.clone().into() }))
}
AnyRef::Named(_) => Err(Error::internal(format!("dangling reference: \"{}\"", reference))),
_ => Err(Error::internal(format!("invalid child reference: \"{}\"", reference))),
}
}
// Return a list of (source, source capability id, target, target capability id) expressed in the
// `offer`.
fn extract_offer_sources_and_targets(
offer: &Offer,
source_names: OneOrMany<Name>,
all_capability_names: &HashSet<Name>,
all_children: &HashSet<&Name>,
all_collections: &HashSet<&Name>,
) -> Result<Vec<(fdecl::Ref, Name, fdecl::Ref, Name)>, Error> {
let mut out = vec![];
let source_names = source_names.to_vec();
let sources = extract_all_offer_sources(offer, all_capability_names, all_collections)?;
let target_names = all_target_capability_names(offer, offer)
.ok_or_else(|| Error::internal("no capability".to_string()))?;
for source in &sources {
for to in &offer.to {
for target_name in &target_names {
// When multiple source names are provided, there is no way to alias each one,
// so we can assume source_name == target_name. When one source name is provided,
// source_name may be aliased to a different target_name, so we use
// source_names[0] to obtain the source_name.
let source_name = if source_names.len() == 1 {
source_names[0].clone()
} else {
target_name.clone()
};
let target =
translate_child_or_collection_ref(to.into(), all_children, all_collections)?;
out.push((source.clone(), source_name, target, target_name.clone()))
}
}
}
Ok(out)
}
/// Return the target paths specified in the given use declaration.
fn all_target_use_paths<T, U>(in_obj: &T, to_obj: &U) -> Option<OneOrMany<Path>>
where
T: CapabilityClause,
U: AsClause + PathClause,
{
if let Some(n) = in_obj.service() {
Some(svc_paths_from_names(n, to_obj))
} else if let Some(n) = in_obj.protocol() {
Some(svc_paths_from_names(n, to_obj))
} else if let Some(_) = in_obj.directory() {
let path = to_obj.path().expect("no path on use directory");
Some(OneOrMany::One(path.clone()))
} else if let Some(_) = in_obj.storage() {
let path = to_obj.path().expect("no path on use storage");
Some(OneOrMany::One(path.clone()))
} else if let Some(_) = in_obj.event_stream_deprecated() {
let path = to_obj.path().expect("no path on event stream");
Some(OneOrMany::One(path.clone()))
} else if let Some(_) = in_obj.event_stream() {
let default_path = Path::new("/svc/fuchsia.component.EventStream".to_string()).unwrap();
let path = to_obj.path().unwrap_or(&default_path);
Some(OneOrMany::One(path.clone()))
} else {
None
}
}
/// Returns the list of paths derived from a `use` declaration with `names` and `to_obj`. `to_obj`
/// must be a declaration that has a `path` clause.
fn svc_paths_from_names<T>(names: OneOrMany<Name>, to_obj: &T) -> OneOrMany<Path>
where
T: PathClause,
{
match names {
OneOrMany::One(n) => {
if let Some(path) = to_obj.path() {
OneOrMany::One(path.clone())
} else {
OneOrMany::One(format!("/svc/{}", n).parse().unwrap())
}
}
OneOrMany::Many(v) => {
let many = v.iter().map(|n| format!("/svc/{}", n).parse().unwrap()).collect();
OneOrMany::Many(many)
}
}
}
/// Return the single target path specified in the given use declaration.
fn one_target_use_path<T, U>(in_obj: &T, to_obj: &U) -> Result<Path, Error>
where
T: CapabilityClause,
U: AsClause + PathClause,
{
match all_target_use_paths(in_obj, to_obj) {
Some(OneOrMany::One(target_name)) => Ok(target_name),
Some(OneOrMany::Many(_)) => {
Err(Error::internal("expecting one capability, but multiple provided"))
}
_ => Err(Error::internal("expecting one capability, but none provided")),
}
}
/// Return the target names or paths specified in the given capability.
fn all_target_capability_names<T, U>(in_obj: &T, to_obj: &U) -> Option<OneOrMany<Name>>
where
T: CapabilityClause,
U: AsClause + PathClause,
{
if let Some(as_) = to_obj.r#as() {
// We've already validated that when `as` is specified, only 1 source id exists.
Some(OneOrMany::One(as_.clone()))
} else {
if let Some(n) = in_obj.service() {
Some(n.clone())
} else if let Some(n) = in_obj.protocol() {
Some(n.clone())
} else if let Some(n) = in_obj.directory() {
Some(n.clone())
} else if let Some(n) = in_obj.storage() {
Some(n.clone())
} else if let Some(n) = in_obj.runner() {
Some(n.clone())
} else if let Some(n) = in_obj.resolver() {
Some(n.clone())
} else if let Some(n) = in_obj.event() {
Some(n.clone())
} else if let Some(n) = in_obj.event_stream() {
Some(n.clone())
} else {
None
}
}
}
fn extract_expose_target(in_obj: &Expose) -> Result<fdecl::Ref, Error> {
match &in_obj.to {
Some(ExposeToRef::Parent) => Ok(fdecl::Ref::Parent(fdecl::ParentRef {})),
Some(ExposeToRef::Framework) => Ok(fdecl::Ref::Framework(fdecl::FrameworkRef {})),
None => Ok(fdecl::Ref::Parent(fdecl::ParentRef {})),
}
}
fn extract_environment_ref(r: Option<&EnvironmentRef>) -> Option<cm::Name> {
r.map(|r| {
let EnvironmentRef::Named(name) = r;
name.clone()
})
}
pub fn translate_capabilities(
capabilities_in: &Vec<Capability>,
as_builtin: bool,
) -> Result<Vec<fdecl::Capability>, Error> {
let mut out_capabilities = vec![];
for capability in capabilities_in {
if let Some(service) = &capability.service {
for n in service.to_vec() {
let source_path = match as_builtin {
true => None,
false => Some(
capability
.path
.clone()
.unwrap_or_else(|| format!("/svc/{}", n).parse().unwrap())
.into(),
),
};
out_capabilities.push(fdecl::Capability::Service(fdecl::Service {
name: Some(n.clone().into()),
source_path: source_path,
..fdecl::Service::EMPTY
}));
}
} else if let Some(protocol) = &capability.protocol {
for n in protocol.to_vec() {
let source_path = match as_builtin {
true => None,
false => Some(
capability
.path
.clone()
.unwrap_or_else(|| format!("/svc/{}", n).parse().unwrap())
.into(),
),
};
out_capabilities.push(fdecl::Capability::Protocol(fdecl::Protocol {
name: Some(n.clone().into()),
source_path: source_path,
..fdecl::Protocol::EMPTY
}));
}
} else if let Some(n) = &capability.directory {
let source_path = match as_builtin {
true => None,
false => {
Some(capability.path.as_ref().expect("missing source path").clone().into())
}
};
let rights = extract_required_rights(capability, "capability")?;
out_capabilities.push(fdecl::Capability::Directory(fdecl::Directory {
name: Some(n.clone().into()),
source_path: source_path,
rights: Some(rights),
..fdecl::Directory::EMPTY
}));
} else if let Some(n) = &capability.storage {
if as_builtin {
return Err(Error::internal(format!(
"built-in storage capabilities are not supported"
)));
}
let backing_dir = capability
.backing_dir
.as_ref()
.expect("storage has no path or backing_dir")
.clone()
.into();
out_capabilities.push(fdecl::Capability::Storage(fdecl::Storage {
name: Some(n.clone().into()),
backing_dir: Some(backing_dir),
source: Some(offer_source_from_ref(
capability.from.as_ref().unwrap().into(),
None,
None,
)?),
subdir: capability.subdir.clone().map(Into::into),
storage_id: Some(
capability.storage_id.clone().expect("storage is missing storage_id").into(),
),
..fdecl::Storage::EMPTY
}));
} else if let Some(n) = &capability.runner {
let source_path = match as_builtin {
true => None,
false => {
Some(capability.path.as_ref().expect("missing source path").clone().into())
}
};
out_capabilities.push(fdecl::Capability::Runner(fdecl::Runner {
name: Some(n.clone().into()),
source_path: source_path,
..fdecl::Runner::EMPTY
}));
} else if let Some(n) = &capability.resolver {
let source_path = match as_builtin {
true => None,
false => {
Some(capability.path.as_ref().expect("missing source path").clone().into())
}
};
out_capabilities.push(fdecl::Capability::Resolver(fdecl::Resolver {
name: Some(n.clone().into()),
source_path: source_path,
..fdecl::Resolver::EMPTY
}));
} else if let Some(n) = &capability.event {
if !as_builtin {
return Err(Error::internal(format!(
"event capabilities may only be declared as built-in capabilities"
)));
}
out_capabilities.push(fdecl::Capability::Event(fdecl::Event {
name: Some(n.clone().into()),
..fdecl::Event::EMPTY
}));
} else if let Some(ns) = &capability.event_stream {
if !as_builtin {
return Err(Error::internal(format!(
"event_stream capabilities may only be declared as built-in capabilities"
)));
}
for n in ns {
out_capabilities.push(fdecl::Capability::EventStream(fdecl::EventStream {
name: Some(n.clone().into()),
..fdecl::EventStream::EMPTY
}));
}
} else {
return Err(Error::internal(format!("no capability declaration recognized")));
}
}
Ok(out_capabilities)
}
pub fn extract_required_rights<T>(in_obj: &T, keyword: &str) -> Result<fio::Operations, Error>
where
T: RightsClause,
{
match in_obj.rights() {
Some(rights_tokens) => {
let mut rights = Vec::new();
for token in rights_tokens.0.iter() {
rights.append(&mut token.expand())
}
if rights.is_empty() {
return Err(Error::missing_rights(format!(
"Rights provided to `{}` are not well formed.",
keyword
)));
}
let mut seen_rights = HashSet::with_capacity(rights.len());
let mut operations: fio::Operations = fio::Operations::empty();
for right in rights.iter() {
if seen_rights.contains(&right) {
return Err(Error::duplicate_rights(format!(
"Rights provided to `{}` are not well formed.",
keyword
)));
}
seen_rights.insert(right);
operations |= *right;
}
Ok(operations)
}
None => Err(Error::internal(format!(
"No `{}` rights provided but required for directories",
keyword
))),
}
}
pub fn offer_source_from_ref(
reference: AnyRef<'_>,
all_capability_names: Option<&HashSet<Name>>,
all_collection_names: Option<&HashSet<&Name>>,
) -> Result<fdecl::Ref, Error> {
match reference {
AnyRef::Named(name) => {
if all_capability_names.is_some() && all_capability_names.unwrap().contains(&name) {
Ok(fdecl::Ref::Capability(fdecl::CapabilityRef { name: name.clone().into() }))
} else if all_collection_names.is_some()
&& all_collection_names.unwrap().contains(&name)
{
Ok(fdecl::Ref::Collection(fdecl::CollectionRef { name: name.clone().into() }))
} else {
Ok(fdecl::Ref::Child(fdecl::ChildRef {
name: name.clone().into(),
collection: None,
}))
}
}
AnyRef::Framework => Ok(fdecl::Ref::Framework(fdecl::FrameworkRef {})),
AnyRef::Debug => Ok(fdecl::Ref::Debug(fdecl::DebugRef {})),
AnyRef::Parent => Ok(fdecl::Ref::Parent(fdecl::ParentRef {})),
AnyRef::Self_ => Ok(fdecl::Ref::Self_(fdecl::SelfRef {})),
AnyRef::Void => Ok(fdecl::Ref::VoidType(fdecl::VoidRef {})),
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::{
error::Error, AnyRef, AsClause, Capability, CapabilityClause, Child, Collection,
DebugRegistration, Document, Environment, EnvironmentExtends, EnvironmentRef,
EventSubscriptionsClause, Expose, ExposeFromRef, ExposeToRef, FromClause, Offer,
OneOrMany, Path, PathClause, Program, ResolverRegistration, RightsClause,
RunnerRegistration, Use, UseFromRef,
},
cm_types::{self as cm, Name},
fidl_fuchsia_component_decl as fdecl, fidl_fuchsia_data as fdata, fidl_fuchsia_io as fio,
serde_json::{json, Map, Value},
std::collections::HashSet,
std::convert::Into,
};
macro_rules! test_compile {
(
$(
$(#[$m:meta])*
$test_name:ident => {
input = $input:expr,
output = $expected:expr,
},
)+
) => {
$(
$(#[$m])*
#[test]
fn $test_name() {
let input = serde_json::from_str(&$input.to_string()).expect("deserialization failed");
let actual = compile(&input, Some("fake.cvf")).expect("compilation failed");
assert_eq!(actual, $expected);
}
)+
}
}
fn default_component_decl() -> fdecl::Component {
fdecl::Component::EMPTY
}
test_compile! {
test_compile_empty => {
input = json!({}),
output = default_component_decl(),
},
test_compile_empty_includes => {
input = json!({ "include": [] }),
output = default_component_decl(),
},
test_compile_program => {
input = json!({
"program": {
"runner": "elf",
"binary": "bin/app",
},
}),
output = fdecl::Component {
program: Some(fdecl::Program {
runner: Some("elf".to_string()),
info: Some(fdata::Dictionary {
entries: Some(vec![fdata::DictionaryEntry {
key: "binary".to_string(),
value: Some(Box::new(fdata::DictionaryValue::Str("bin/app".to_string()))),
}]),
..fdata::Dictionary::EMPTY
}),
..fdecl::Program::EMPTY
}),
..default_component_decl()
},
},
test_compile_program_with_nested_objects => {
input = json!({
"program": {
"runner": "elf",
"binary": "bin/app",
"one": {
"two": {
"three.four": {
"five": "six"
}
},
}
},
}),
output = fdecl::Component {
program: Some(fdecl::Program {
runner: Some("elf".to_string()),
info: Some(fdata::Dictionary {
entries: Some(vec![
fdata::DictionaryEntry {
key: "binary".to_string(),
value: Some(Box::new(fdata::DictionaryValue::Str("bin/app".to_string()))),
},
fdata::DictionaryEntry {
key: "one.two.three.four.five".to_string(),
value: Some(Box::new(fdata::DictionaryValue::Str("six".to_string()))),
},
]),
..fdata::Dictionary::EMPTY
}),
..fdecl::Program::EMPTY
}),
..default_component_decl()
},
},
test_compile_program_with_array_of_objects => {
input = json!({
"program": {
"runner": "elf",
"binary": "bin/app",
"networks": [
{
"endpoints": [
{
"name": "device",
"mac": "aa:bb:cc:dd:ee:ff"
},
{
"name": "emu",
"mac": "ff:ee:dd:cc:bb:aa"
},
],
"name": "external_network"
}
],
},
}),
output = fdecl::Component {
program: Some(fdecl::Program {
runner: Some("elf".to_string()),
info: Some(fdata::Dictionary {
entries: Some(vec![
fdata::DictionaryEntry {
key: "binary".to_string(),
value: Some(Box::new(fdata::DictionaryValue::Str("bin/app".to_string()))),
},
fdata::DictionaryEntry {
key: "networks".to_string(),
value: Some(Box::new(fdata::DictionaryValue::ObjVec(vec![
fdata::Dictionary {
entries: Some(vec![
fdata::DictionaryEntry {
key: "endpoints".to_string(),
value: Some(Box::new(fdata::DictionaryValue::ObjVec(vec![
fdata::Dictionary {
entries: Some(vec![
fdata::DictionaryEntry {
key: "mac".to_string(),
value: Some(Box::new(fdata::DictionaryValue::Str("aa:bb:cc:dd:ee:ff".to_string()))),
},
fdata::DictionaryEntry {
key: "name".to_string(),
value: Some(Box::new(fdata::DictionaryValue::Str("device".to_string()))),
}
]),
..fdata::Dictionary::EMPTY
},
fdata::Dictionary {
entries: Some(vec![
fdata::DictionaryEntry {
key: "mac".to_string(),
value: Some(Box::new(fdata::DictionaryValue::Str("ff:ee:dd:cc:bb:aa".to_string()))),
},
fdata::DictionaryEntry {
key: "name".to_string(),
value: Some(Box::new(fdata::DictionaryValue::Str("emu".to_string()))),
}
]),
..fdata::Dictionary::EMPTY
},
])))
},
fdata::DictionaryEntry {
key: "name".to_string(),
value: Some(Box::new(fdata::DictionaryValue::Str("external_network".to_string()))),
},
]),
..fdata::Dictionary::EMPTY
}
]))),
},
]),
..fdata::Dictionary::EMPTY
}),
..fdecl::Program::EMPTY
}),
..default_component_decl()
},
},
test_compile_use => {
input = json!({
"use": [
{
"protocol": "LegacyCoolFonts",
"path": "/svc/fuchsia.fonts.LegacyProvider",
"availability": "optional",
},
{ "protocol": "fuchsia.sys2.LegacyRealm", "from": "framework" },
{ "protocol": "fuchsia.sys2.StorageAdmin", "from": "#data-storage" },
{ "protocol": "fuchsia.sys2.DebugProto", "from": "debug" },
{ "protocol": "fuchsia.sys2.Echo", "from": "self", "availability": "optional" },
{ "directory": "assets", "rights" : ["read_bytes"], "path": "/data/assets" },
{
"directory": "config",
"path": "/data/config",
"from": "parent",
"rights": ["read_bytes"],
"subdir": "fonts",
},
{ "storage": "hippos", "path": "/hippos" },
{ "storage": "cache", "path": "/tmp" },
{ "event": "destroyed", "from": "parent" },
{ "event": ["started", "stopped"], "from": "framework" },
{
"event": "directory_ready",
"as": "diagnostics",
"from": "parent",
"filter": { "name": "diagnostics" }
},
{
"event_stream_deprecated": "foo_stream",
"subscriptions": [
{
"event": [ "started", "diagnostics" ],
},
{
"event": [ "destroyed" ],
}
]
},
{
"event_stream": "bar_stream",
},
{
"event_stream": ["foobar", "stream"],
"scope": ["#logger", "#modular"]
}
],
"capabilities": [
{
"storage": "data-storage",
"from": "parent",
"backing_dir": "minfs",
"storage_id": "static_instance_id_or_moniker",
}
],
"children": [
{
"name": "logger",
"url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
"environment": "#env_one"
}
],
"collections": [
{
"name": "modular",
"durability": "persistent",
},
],
}
),
output = fdecl::Component {
uses: Some(vec![
fdecl::Use::Protocol (
fdecl::UseProtocol {
dependency_type: Some(fdecl::DependencyType::Strong),
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
source_name: Some("LegacyCoolFonts".to_string()),
target_path: Some("/svc/fuchsia.fonts.LegacyProvider".to_string()),
availability: Some(fdecl::Availability::Optional),
..fdecl::UseProtocol::EMPTY
}
),
fdecl::Use::Protocol (
fdecl::UseProtocol {
dependency_type: Some(fdecl::DependencyType::Strong),
source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef {})),
source_name: Some("fuchsia.sys2.LegacyRealm".to_string()),
target_path: Some("/svc/fuchsia.sys2.LegacyRealm".to_string()),
availability: Some(fdecl::Availability::Required),
..fdecl::UseProtocol::EMPTY
}
),
fdecl::Use::Protocol (
fdecl::UseProtocol {
dependency_type: Some(fdecl::DependencyType::Strong),
source: Some(fdecl::Ref::Capability(fdecl::CapabilityRef { name: "data-storage".to_string() })),
source_name: Some("fuchsia.sys2.StorageAdmin".to_string()),
target_path: Some("/svc/fuchsia.sys2.StorageAdmin".to_string()),
availability: Some(fdecl::Availability::Required),
..fdecl::UseProtocol::EMPTY
}
),
fdecl::Use::Protocol (
fdecl::UseProtocol {
dependency_type: Some(fdecl::DependencyType::Strong),
source: Some(fdecl::Ref::Debug(fdecl::DebugRef {})),
source_name: Some("fuchsia.sys2.DebugProto".to_string()),
target_path: Some("/svc/fuchsia.sys2.DebugProto".to_string()),
availability: Some(fdecl::Availability::Required),
..fdecl::UseProtocol::EMPTY
}
),
fdecl::Use::Protocol (
fdecl::UseProtocol {
dependency_type: Some(fdecl::DependencyType::Strong),
source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
source_name: Some("fuchsia.sys2.Echo".to_string()),
target_path: Some("/svc/fuchsia.sys2.Echo".to_string()),
availability: Some(fdecl::Availability::Optional),
..fdecl::UseProtocol::EMPTY
}
),
fdecl::Use::Directory (
fdecl::UseDirectory {
dependency_type: Some(fdecl::DependencyType::Strong),
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
source_name: Some("assets".to_string()),
target_path: Some("/data/assets".to_string()),
rights: Some(fio::Operations::READ_BYTES),
subdir: None,
availability: Some(fdecl::Availability::Required),
..fdecl::UseDirectory::EMPTY
}
),
fdecl::Use::Directory (
fdecl::UseDirectory {
dependency_type: Some(fdecl::DependencyType::Strong),
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
source_name: Some("config".to_string()),
target_path: Some("/data/config".to_string()),
rights: Some(fio::Operations::READ_BYTES),
subdir: Some("fonts".to_string()),
availability: Some(fdecl::Availability::Required),
..fdecl::UseDirectory::EMPTY
}
),
fdecl::Use::Storage (
fdecl::UseStorage {
source_name: Some("hippos".to_string()),
target_path: Some("/hippos".to_string()),
availability: Some(fdecl::Availability::Required),
..fdecl::UseStorage::EMPTY
}
),
fdecl::Use::Storage (
fdecl::UseStorage {
source_name: Some("cache".to_string()),
target_path: Some("/tmp".to_string()),
availability: Some(fdecl::Availability::Required),
..fdecl::UseStorage::EMPTY
}
),
fdecl::Use::Event (
fdecl::UseEvent {
dependency_type: Some(fdecl::DependencyType::Strong),
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
source_name: Some("destroyed".to_string()),
target_name: Some("destroyed".to_string()),
filter: None,
availability: Some(fdecl::Availability::Required),
..fdecl::UseEvent::EMPTY
}
),
fdecl::Use::Event (
fdecl::UseEvent {
dependency_type: Some(fdecl::DependencyType::Strong),
source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef {})),
source_name: Some("started".to_string()),
target_name: Some("started".to_string()),
filter: None,
availability: Some(fdecl::Availability::Required),
..fdecl::UseEvent::EMPTY
}
),
fdecl::Use::Event (
fdecl::UseEvent {
dependency_type: Some(fdecl::DependencyType::Strong),
source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef {})),
source_name: Some("stopped".to_string()),
target_name: Some("stopped".to_string()),
filter: None,
availability: Some(fdecl::Availability::Required),
..fdecl::UseEvent::EMPTY
}
),
fdecl::Use::Event (
fdecl::UseEvent {
dependency_type: Some(fdecl::DependencyType::Strong),
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
source_name: Some("directory_ready".to_string()),
target_name: Some("diagnostics".to_string()),
filter: Some(fdata::Dictionary {
entries: Some(vec![
fdata::DictionaryEntry {
key: "name".to_string(),
value: Some(Box::new(fdata::DictionaryValue::Str("diagnostics".to_string()))),
},
]),
..fdata::Dictionary::EMPTY
}),
availability: Some(fdecl::Availability::Required),
..fdecl::UseEvent::EMPTY
}
),
fdecl::Use::EventStreamDeprecated(fdecl::UseEventStreamDeprecated {
name: Some("foo_stream".to_string()),
subscriptions: Some(vec![
fdecl::EventSubscription {
event_name: Some("started".to_string()),
..fdecl::EventSubscription::EMPTY
},
fdecl::EventSubscription {
event_name: Some("diagnostics".to_string()),
..fdecl::EventSubscription::EMPTY
},
fdecl::EventSubscription {
event_name: Some("destroyed".to_string()),
..fdecl::EventSubscription::EMPTY
},
]),
availability: Some(fdecl::Availability::Required),
..fdecl::UseEventStreamDeprecated::EMPTY
}),
fdecl::Use::EventStream(fdecl::UseEventStream {
source_name: Some("bar_stream".to_string()),
source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
target_path: Some("/svc/fuchsia.component.EventStream".to_string()),
availability: Some(fdecl::Availability::Required),
..fdecl::UseEventStream::EMPTY
}),
fdecl::Use::EventStream(fdecl::UseEventStream {
source_name: Some("foobar".to_string()),
scope: Some(vec![fdecl::Ref::Child(fdecl::ChildRef{name:"logger".to_string(), collection: None}), fdecl::Ref::Collection(fdecl::CollectionRef{name:"modular".to_string()})]),
source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
target_path: Some("/svc/fuchsia.component.EventStream".to_string()),
availability: Some(fdecl::Availability::Required),
..fdecl::UseEventStream::EMPTY
}),
fdecl::Use::EventStream(fdecl::UseEventStream {
source_name: Some("stream".to_string()),
scope: Some(vec![fdecl::Ref::Child(fdecl::ChildRef{name:"logger".to_string(), collection: None}), fdecl::Ref::Collection(fdecl::CollectionRef{name:"modular".to_string()})]),
source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})),
target_path: Some("/svc/fuchsia.component.EventStream".to_string()),
availability: Some(fdecl::Availability::Required),
..fdecl::UseEventStream::EMPTY
})
]),
collections:Some(vec![
fdecl::Collection{
name:Some("modular".to_string()),
durability:Some(fdecl::Durability::Persistent),
..fdecl::Collection::EMPTY
},
]),
capabilities: Some(vec![
fdecl::Capability::Storage(fdecl::Storage {
name: Some("data-storage".to_string()),
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
backing_dir: Some("minfs".to_string()),
subdir: None,
storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker),
..fdecl::Storage::EMPTY
}),
]),
children: Some(vec![
fdecl::Child{
name:Some("logger".to_string()),
url:Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
startup:Some(fdecl::StartupMode::Lazy),
environment: Some("env_one".to_string()),
..fdecl::Child::EMPTY
}
]),
..default_component_decl()
},
},
test_compile_expose => {
input = json!({
"expose": [
{
"protocol": "fuchsia.logger.Log",
"from": "#logger",
"as": "fuchsia.logger.LegacyLog",
"to": "parent"
},
{
"protocol": [ "A", "B" ],
"from": "self",
"to": "parent"
},
{
"protocol": "C",
"from": "#data-storage",
},
{
"directory": "blob",
"from": "self",
"to": "framework",
"rights": ["r*"],
},
{
"directory": [ "blob2", "blob3" ],
"from": "#logger",
"to": "parent",
},
{ "directory": "hub", "from": "framework" },
{ "runner": "web", "from": "#logger", "to": "parent", "as": "web-rename" },
{ "runner": [ "runner_a", "runner_b" ], "from": "#logger" },
{ "resolver": "my_resolver", "from": "#logger", "to": "parent", "as": "pkg_resolver" },
{ "resolver": [ "resolver_a", "resolver_b" ], "from": "#logger" },
{
"event_stream": ["started", "stopped"],
"from": "#logger",
"to": "parent",
},
{
"event_stream": "running",
"as": "running_stream",
"from": "#logger",
"to": "parent",
},
],
"capabilities": [
{ "protocol": "A" },
{ "protocol": "B" },
{
"directory": "blob",
"path": "/volumes/blobfs/blob",
"rights": ["r*"],
},
{
"runner": "web",
"path": "/svc/fuchsia.component.ComponentRunner",
},
{
"storage": "data-storage",
"from": "parent",
"backing_dir": "minfs",
"storage_id": "static_instance_id_or_moniker",
},
],
"children": [
{
"name": "logger",
"url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
},
],
}),
output = fdecl::Component {
exposes: Some(vec![
fdecl::Expose::Protocol (
fdecl::ExposeProtocol {
source: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("fuchsia.logger.Log".to_string()),
target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
target_name: Some("fuchsia.logger.LegacyLog".to_string()),
..fdecl::ExposeProtocol::EMPTY
}
),
fdecl::Expose::Protocol (
fdecl::ExposeProtocol {
source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
source_name: Some("A".to_string()),
target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
target_name: Some("A".to_string()),
..fdecl::ExposeProtocol::EMPTY
}
),
fdecl::Expose::Protocol (
fdecl::ExposeProtocol {
source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
source_name: Some("B".to_string()),
target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
target_name: Some("B".to_string()),
..fdecl::ExposeProtocol::EMPTY
}
),
fdecl::Expose::Protocol (
fdecl::ExposeProtocol {
source: Some(fdecl::Ref::Capability(fdecl::CapabilityRef {
name: "data-storage".to_string(),
})),
source_name: Some("C".to_string()),
target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
target_name: Some("C".to_string()),
..fdecl::ExposeProtocol::EMPTY
}
),
fdecl::Expose::Directory (
fdecl::ExposeDirectory {
source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
source_name: Some("blob".to_string()),
target: Some(fdecl::Ref::Framework(fdecl::FrameworkRef {})),
target_name: Some("blob".to_string()),
rights: Some(
fio::Operations::CONNECT | fio::Operations::ENUMERATE |
fio::Operations::TRAVERSE | fio::Operations::READ_BYTES |
fio::Operations::GET_ATTRIBUTES
),
subdir: None,
..fdecl::ExposeDirectory::EMPTY
}
),
fdecl::Expose::Directory (
fdecl::ExposeDirectory {
source: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("blob2".to_string()),
target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
target_name: Some("blob2".to_string()),
rights: None,
subdir: None,
..fdecl::ExposeDirectory::EMPTY
}
),
fdecl::Expose::Directory (
fdecl::ExposeDirectory {
source: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("blob3".to_string()),
target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
target_name: Some("blob3".to_string()),
rights: None,
subdir: None,
..fdecl::ExposeDirectory::EMPTY
}
),
fdecl::Expose::Directory (
fdecl::ExposeDirectory {
source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef {})),
source_name: Some("hub".to_string()),
target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
target_name: Some("hub".to_string()),
rights: None,
subdir: None,
..fdecl::ExposeDirectory::EMPTY
}
),
fdecl::Expose::Runner (
fdecl::ExposeRunner {
source: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("web".to_string()),
target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
target_name: Some("web-rename".to_string()),
..fdecl::ExposeRunner::EMPTY
}
),
fdecl::Expose::Runner (
fdecl::ExposeRunner {
source: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("runner_a".to_string()),
target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
target_name: Some("runner_a".to_string()),
..fdecl::ExposeRunner::EMPTY
}
),
fdecl::Expose::Runner (
fdecl::ExposeRunner {
source: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("runner_b".to_string()),
target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
target_name: Some("runner_b".to_string()),
..fdecl::ExposeRunner::EMPTY
}
),
fdecl::Expose::Resolver (
fdecl::ExposeResolver {
source: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("my_resolver".to_string()),
target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
target_name: Some("pkg_resolver".to_string()),
..fdecl::ExposeResolver::EMPTY
}
),
fdecl::Expose::Resolver (
fdecl::ExposeResolver {
source: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("resolver_a".to_string()),
target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
target_name: Some("resolver_a".to_string()),
..fdecl::ExposeResolver::EMPTY
}
),
fdecl::Expose::Resolver (
fdecl::ExposeResolver {
source: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("resolver_b".to_string()),
target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
target_name: Some("resolver_b".to_string()),
..fdecl::ExposeResolver::EMPTY
}
),
fdecl::Expose::EventStream (
fdecl::ExposeEventStream {
source: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("started".to_string()),
target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
target_name: Some("started".to_string()),
..fdecl::ExposeEventStream::EMPTY
}
),
fdecl::Expose::EventStream (
fdecl::ExposeEventStream {
source: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("stopped".to_string()),
target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
target_name: Some("stopped".to_string()),
..fdecl::ExposeEventStream::EMPTY
}
),
fdecl::Expose::EventStream (
fdecl::ExposeEventStream {
source: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("running".to_string()),
target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
target_name: Some("running_stream".to_string()),
..fdecl::ExposeEventStream::EMPTY
}
),
]),
offers: None,
capabilities: Some(vec![
fdecl::Capability::Protocol (
fdecl::Protocol {
name: Some("A".to_string()),
source_path: Some("/svc/A".to_string()),
..fdecl::Protocol::EMPTY
}
),
fdecl::Capability::Protocol (
fdecl::Protocol {
name: Some("B".to_string()),
source_path: Some("/svc/B".to_string()),
..fdecl::Protocol::EMPTY
}
),
fdecl::Capability::Directory (
fdecl::Directory {
name: Some("blob".to_string()),
source_path: Some("/volumes/blobfs/blob".to_string()),
rights: Some(fio::Operations::CONNECT | fio::Operations::ENUMERATE |
fio::Operations::TRAVERSE | fio::Operations::READ_BYTES |
fio::Operations::GET_ATTRIBUTES
),
..fdecl::Directory::EMPTY
}
),
fdecl::Capability::Runner (
fdecl::Runner {
name: Some("web".to_string()),
source_path: Some("/svc/fuchsia.component.ComponentRunner".to_string()),
..fdecl::Runner::EMPTY
}
),
fdecl::Capability::Storage(fdecl::Storage {
name: Some("data-storage".to_string()),
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
backing_dir: Some("minfs".to_string()),
subdir: None,
storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker),
..fdecl::Storage::EMPTY
}),
]),
children: Some(vec![
fdecl::Child {
name: Some("logger".to_string()),
url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
startup: Some(fdecl::StartupMode::Lazy),
environment: None,
on_terminate: None,
..fdecl::Child::EMPTY
}
]),
..default_component_decl()
},
},
test_compile_offer => {
input = json!({
"offer": [
{
"protocol": "fuchsia.logger.LegacyLog",
"from": "#logger",
"to": "#netstack", // Verifies compilation of singleton "to:".
"dependency": "weak"
},
{
"protocol": "fuchsia.logger.LegacyLog",
"from": "#logger",
"to": [ "#modular" ], // Verifies compilation of "to:" as array of one element.
"as": "fuchsia.logger.LegacySysLog",
"dependency": "strong"
},
{
"protocol": "fuchsia.logger.LegacyLog2",
"from": "#non-existent",
"to": [ "#modular" ], // Verifies compilation of "to:" as array of one element.
"as": "fuchsia.logger.LegacySysLog2",
"dependency": "strong",
"availability": "optional",
"source_availability": "unknown"
},
{
"protocol": [
"fuchsia.setui.SetUiService",
"fuchsia.test.service.Name"
],
"from": "parent",
"to": [ "#modular" ],
"availability": "optional"
},
{
"protocol": "fuchsia.sys2.StorageAdmin",
"from": "#data",
"to": [ "#modular" ],
},
{
"directory": "assets",
"from": "parent",
"to": [ "#netstack" ],
"dependency": "weak_for_migration",
"availability": "same_as_target"
},
{
"directory": [ "assets2", "assets3" ],
"from": "parent",
"to": [ "#modular", "#netstack" ],
},
{
"directory": "data",
"from": "parent",
"to": [ "#modular" ],
"as": "assets",
"subdir": "index/file",
"dependency": "strong"
},
{
"directory": "hub",
"from": "framework",
"to": [ "#modular" ],
"as": "hub",
},
{
"storage": "data",
"from": "self",
"to": [
"#netstack",
"#modular"
],
},
{
"storage": [ "storage_a", "storage_b" ],
"from": "parent",
"to": "#netstack",
},
{
"runner": "elf",
"from": "parent",
"to": [ "#modular" ],
"as": "elf-renamed",
},
{
"runner": [ "runner_a", "runner_b" ],
"from": "parent",
"to": "#netstack",
},
{
"event": "destroyed",
"from": "framework",
"to": [ "#netstack"],
"as": "destroyed_net"
},
{
"event": [ "stopped", "started" ],
"from": "parent",
"to": [ "#modular" ],
},
{
"event": "directory_ready",
"from": "parent",
"to": [ "#netstack" ],
"as": "net-ready",
"filter": {
"name": [
"diagnostics",
"foo"
],
}
},
{
"resolver": "my_resolver",
"from": "parent",
"to": [ "#modular" ],
"as": "pkg_resolver",
},
{
"resolver": [ "resolver_a", "resolver_b" ],
"from": "parent",
"to": "#netstack",
},
{
"event_stream": [
"running",
"started",
],
"from": "parent",
"to": "#netstack",
},
{
"event_stream": "stopped",
"from": "parent",
"to": "#netstack",
"as": "some_other_event",
"filter": { "name": "diagnostics" }
},
],
"children": [
{
"name": "logger",
"url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
},
{
"name": "netstack",
"url": "fuchsia-pkg://fuchsia.com/netstack/stable#meta/netstack.cm"
},
],
"collections": [
{
"name": "modular",
"durability": "persistent",
},
],
"capabilities": [
{
"storage": "data",
"backing_dir": "minfs",
"from": "#logger",
"storage_id": "static_instance_id_or_moniker",
},
],
}),
output = fdecl::Component {
offers: Some(vec![
fdecl::Offer::Protocol (
fdecl::OfferProtocol {
source: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("fuchsia.logger.LegacyLog".to_string()),
target: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "netstack".to_string(),
collection: None,
})),
target_name: Some("fuchsia.logger.LegacyLog".to_string()),
dependency_type: Some(fdecl::DependencyType::Weak),
availability: Some(fdecl::Availability::Required),
..fdecl::OfferProtocol::EMPTY
}
),
fdecl::Offer::Protocol (
fdecl::OfferProtocol {
source: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("fuchsia.logger.LegacyLog".to_string()),
target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
name: "modular".to_string(),
})),
target_name: Some("fuchsia.logger.LegacySysLog".to_string()),
dependency_type: Some(fdecl::DependencyType::Strong),
availability: Some(fdecl::Availability::Required),
..fdecl::OfferProtocol::EMPTY
}
),
fdecl::Offer::Protocol (
fdecl::OfferProtocol {
source: Some(fdecl::Ref::VoidType(fdecl::VoidRef {})),
source_name: Some("fuchsia.logger.LegacyLog2".to_string()),
target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
name: "modular".to_string(),
})),
target_name: Some("fuchsia.logger.LegacySysLog2".to_string()),
dependency_type: Some(fdecl::DependencyType::Strong),
availability: Some(fdecl::Availability::Optional),
..fdecl::OfferProtocol::EMPTY
}
),
fdecl::Offer::Protocol (
fdecl::OfferProtocol {
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
source_name: Some("fuchsia.setui.SetUiService".to_string()),
target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
name: "modular".to_string(),
})),
target_name: Some("fuchsia.setui.SetUiService".to_string()),
dependency_type: Some(fdecl::DependencyType::Strong),
availability: Some(fdecl::Availability::Optional),
..fdecl::OfferProtocol::EMPTY
}
),
fdecl::Offer::Protocol (
fdecl::OfferProtocol {
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
source_name: Some("fuchsia.test.service.Name".to_string()),
target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
name: "modular".to_string(),
})),
target_name: Some("fuchsia.test.service.Name".to_string()),
dependency_type: Some(fdecl::DependencyType::Strong),
availability: Some(fdecl::Availability::Optional),
..fdecl::OfferProtocol::EMPTY
}
),
fdecl::Offer::Protocol (
fdecl::OfferProtocol {
source: Some(fdecl::Ref::Capability(fdecl::CapabilityRef {
name: "data".to_string(),
})),
source_name: Some("fuchsia.sys2.StorageAdmin".to_string()),
target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
name: "modular".to_string(),
})),
target_name: Some("fuchsia.sys2.StorageAdmin".to_string()),
dependency_type: Some(fdecl::DependencyType::Strong),
availability: Some(fdecl::Availability::Required),
..fdecl::OfferProtocol::EMPTY
}
),
fdecl::Offer::Directory (
fdecl::OfferDirectory {
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
source_name: Some("assets".to_string()),
target: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "netstack".to_string(),
collection: None,
})),
target_name: Some("assets".to_string()),
rights: None,
subdir: None,
dependency_type: Some(fdecl::DependencyType::WeakForMigration),
availability: Some(fdecl::Availability::SameAsTarget),
..fdecl::OfferDirectory::EMPTY
}
),
fdecl::Offer::Directory (
fdecl::OfferDirectory {
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
source_name: Some("assets2".to_string()),
target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
name: "modular".to_string(),
})),
target_name: Some("assets2".to_string()),
rights: None,
subdir: None,
dependency_type: Some(fdecl::DependencyType::Strong),
availability: Some(fdecl::Availability::Required),
..fdecl::OfferDirectory::EMPTY
}
),
fdecl::Offer::Directory (
fdecl::OfferDirectory {
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
source_name: Some("assets3".to_string()),
target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
name: "modular".to_string(),
})),
target_name: Some("assets3".to_string()),
rights: None,
subdir: None,
dependency_type: Some(fdecl::DependencyType::Strong),
availability: Some(fdecl::Availability::Required),
..fdecl::OfferDirectory::EMPTY
}
),
fdecl::Offer::Directory (
fdecl::OfferDirectory {
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
source_name: Some("assets2".to_string()),
target: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "netstack".to_string(),
collection: None,
})),
target_name: Some("assets2".to_string()),
rights: None,
subdir: None,
dependency_type: Some(fdecl::DependencyType::Strong),
availability: Some(fdecl::Availability::Required),
..fdecl::OfferDirectory::EMPTY
}
),
fdecl::Offer::Directory (
fdecl::OfferDirectory {
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
source_name: Some("assets3".to_string()),
target: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "netstack".to_string(),
collection: None,
})),
target_name: Some("assets3".to_string()),
rights: None,
subdir: None,
dependency_type: Some(fdecl::DependencyType::Strong),
availability: Some(fdecl::Availability::Required),
..fdecl::OfferDirectory::EMPTY
}
),
fdecl::Offer::Directory (
fdecl::OfferDirectory {
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
source_name: Some("data".to_string()),
target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
name: "modular".to_string(),
})),
target_name: Some("assets".to_string()),
rights: None,
subdir: Some("index/file".to_string()),
dependency_type: Some(fdecl::DependencyType::Strong),
availability: Some(fdecl::Availability::Required),
..fdecl::OfferDirectory::EMPTY
}
),
fdecl::Offer::Directory (
fdecl::OfferDirectory {
source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef {})),
source_name: Some("hub".to_string()),
target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
name: "modular".to_string(),
})),
target_name: Some("hub".to_string()),
rights: None,
subdir: None,
dependency_type: Some(fdecl::DependencyType::Strong),
availability: Some(fdecl::Availability::Required),
..fdecl::OfferDirectory::EMPTY
}
),
fdecl::Offer::Storage (
fdecl::OfferStorage {
source_name: Some("data".to_string()),
source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
target: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "netstack".to_string(),
collection: None,
})),
target_name: Some("data".to_string()),
availability: Some(fdecl::Availability::Required),
..fdecl::OfferStorage::EMPTY
}
),
fdecl::Offer::Storage (
fdecl::OfferStorage {
source_name: Some("data".to_string()),
source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
name: "modular".to_string(),
})),
target_name: Some("data".to_string()),
availability: Some(fdecl::Availability::Required),
..fdecl::OfferStorage::EMPTY
}
),
fdecl::Offer::Storage (
fdecl::OfferStorage {
source_name: Some("storage_a".to_string()),
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
target: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "netstack".to_string(),
collection: None,
})),
target_name: Some("storage_a".to_string()),
availability: Some(fdecl::Availability::Required),
..fdecl::OfferStorage::EMPTY
}
),
fdecl::Offer::Storage (
fdecl::OfferStorage {
source_name: Some("storage_b".to_string()),
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
target: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "netstack".to_string(),
collection: None,
})),
target_name: Some("storage_b".to_string()),
availability: Some(fdecl::Availability::Required),
..fdecl::OfferStorage::EMPTY
}
),
fdecl::Offer::Runner (
fdecl::OfferRunner {
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
source_name: Some("elf".to_string()),
target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
name: "modular".to_string(),
})),
target_name: Some("elf-renamed".to_string()),
..fdecl::OfferRunner::EMPTY
}
),
fdecl::Offer::Runner (
fdecl::OfferRunner {
source_name: Some("runner_a".to_string()),
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
target: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "netstack".to_string(),
collection: None,
})),
target_name: Some("runner_a".to_string()),
..fdecl::OfferRunner::EMPTY
}
),
fdecl::Offer::Runner (
fdecl::OfferRunner {
source_name: Some("runner_b".to_string()),
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
target: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "netstack".to_string(),
collection: None,
})),
target_name: Some("runner_b".to_string()),
..fdecl::OfferRunner::EMPTY
}
),
fdecl::Offer::Event (
fdecl::OfferEvent {
source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef {})),
source_name: Some("destroyed".to_string()),
target: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "netstack".to_string(),
collection: None,
})),
target_name: Some("destroyed_net".to_string()),
filter: None,
availability: Some(fdecl::Availability::Required),
..fdecl::OfferEvent::EMPTY
}
),
fdecl::Offer::Event (
fdecl::OfferEvent {
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
source_name: Some("stopped".to_string()),
target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
name: "modular".to_string(),
})),
target_name: Some("stopped".to_string()),
filter: None,
availability: Some(fdecl::Availability::Required),
..fdecl::OfferEvent::EMPTY
}
),
fdecl::Offer::Event (
fdecl::OfferEvent {
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
source_name: Some("started".to_string()),
target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
name: "modular".to_string(),
})),
target_name: Some("started".to_string()),
filter: None,
availability: Some(fdecl::Availability::Required),
..fdecl::OfferEvent::EMPTY
}
),
fdecl::Offer::Event (
fdecl::OfferEvent {
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
source_name: Some("directory_ready".to_string()),
target: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "netstack".to_string(),
collection: None,
})),
target_name: Some("net-ready".to_string()),
filter: Some(fdata::Dictionary {
entries: Some(vec![
fdata::DictionaryEntry {
key: "name".to_string(),
value: Some(Box::new(fdata::DictionaryValue::StrVec(
vec!["diagnostics".to_string(), "foo".to_string()]
))),
},
]),
..fdata::Dictionary::EMPTY
}),
availability: Some(fdecl::Availability::Required),
..fdecl::OfferEvent::EMPTY
}
),
fdecl::Offer::Resolver (
fdecl::OfferResolver {
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
source_name: Some("my_resolver".to_string()),
target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
name: "modular".to_string(),
})),
target_name: Some("pkg_resolver".to_string()),
..fdecl::OfferResolver::EMPTY
}
),
fdecl::Offer::Resolver (
fdecl::OfferResolver {
source_name: Some("resolver_a".to_string()),
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
target: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "netstack".to_string(),
collection: None,
})),
target_name: Some("resolver_a".to_string()),
..fdecl::OfferResolver::EMPTY
}
),
fdecl::Offer::Resolver (
fdecl::OfferResolver {
source_name: Some("resolver_b".to_string()),
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
target: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "netstack".to_string(),
collection: None,
})),
target_name: Some("resolver_b".to_string()),
..fdecl::OfferResolver::EMPTY
}
),
fdecl::Offer::EventStream (
fdecl::OfferEventStream {
source_name: Some("running".to_string()),
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
target: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "netstack".to_string(),
collection: None,
})),
target_name: Some("running".to_string()),
availability: Some(fdecl::Availability::Required),
..fdecl::OfferEventStream::EMPTY
}
),
fdecl::Offer::EventStream (
fdecl::OfferEventStream {
source_name: Some("started".to_string()),
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
target: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "netstack".to_string(),
collection: None,
})),
target_name: Some("started".to_string()),
availability: Some(fdecl::Availability::Required),
..fdecl::OfferEventStream::EMPTY
}
),
fdecl::Offer::EventStream (
fdecl::OfferEventStream {
source_name: Some("stopped".to_string()),
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
target: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "netstack".to_string(),
collection: None,
})),
filter: Some(fdata::Dictionary {
entries: Some(vec![
fdata::DictionaryEntry {
key: "name".to_string(),
value: Some(
Box::new(
fdata::DictionaryValue::Str(
"diagnostics".to_string()
)
)
),
},
]),
..fdata::Dictionary::EMPTY
}),
target_name: Some("some_other_event".to_string()),
availability: Some(fdecl::Availability::Required),
..fdecl::OfferEventStream::EMPTY
}
),
]),
capabilities: Some(vec![
fdecl::Capability::Storage (
fdecl::Storage {
name: Some("data".to_string()),
source: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "logger".to_string(),
collection: None,
})),
backing_dir: Some("minfs".to_string()),
subdir: None,
storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker),
..fdecl::Storage::EMPTY
}
)
]),
children: Some(vec![
fdecl::Child {
name: Some("logger".to_string()),
url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
startup: Some(fdecl::StartupMode::Lazy),
environment: None,
on_terminate: None,
..fdecl::Child::EMPTY
},
fdecl::Child {
name: Some("netstack".to_string()),
url: Some("fuchsia-pkg://fuchsia.com/netstack/stable#meta/netstack.cm".to_string()),
startup: Some(fdecl::StartupMode::Lazy),
environment: None,
on_terminate: None,
..fdecl::Child::EMPTY
},
]),
collections: Some(vec![
fdecl::Collection {
name: Some("modular".to_string()),
durability: Some(fdecl::Durability::Persistent),
environment: None,
allowed_offers: None,
..fdecl::Collection::EMPTY
}
]),
..default_component_decl()
},
},
test_compile_children => {
input = json!({
"children": [
{
"name": "logger",
"url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
},
{
"name": "gmail",
"url": "https://www.google.com/gmail",
"startup": "eager",
},
{
"name": "echo",
"url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo.cm",
"startup": "lazy",
"on_terminate": "reboot",
"environment": "#myenv",
},
],
"environments": [
{
"name": "myenv",
"extends": "realm",
},
],
}),
output = fdecl::Component {
children: Some(vec![
fdecl::Child {
name: Some("logger".to_string()),
url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
startup: Some(fdecl::StartupMode::Lazy),
environment: None,
on_terminate: None,
..fdecl::Child::EMPTY
},
fdecl::Child {
name: Some("gmail".to_string()),
url: Some("https://www.google.com/gmail".to_string()),
startup: Some(fdecl::StartupMode::Eager),
environment: None,
on_terminate: None,
..fdecl::Child::EMPTY
},
fdecl::Child {
name: Some("echo".to_string()),
url: Some("fuchsia-pkg://fuchsia.com/echo/stable#meta/echo.cm".to_string()),
startup: Some(fdecl::StartupMode::Lazy),
environment: Some("myenv".to_string()),
on_terminate: Some(fdecl::OnTerminate::Reboot),
..fdecl::Child::EMPTY
}
]),
environments: Some(vec![
fdecl::Environment {
name: Some("myenv".to_string()),
extends: Some(fdecl::EnvironmentExtends::Realm),
runners: None,
resolvers: None,
stop_timeout_ms: None,
..fdecl::Environment::EMPTY
}
]),
..default_component_decl()
},
},
test_compile_collections => {
input = json!({
"collections": [
{
"name": "modular",
"durability": "persistent",
},
{
"name": "tests",
"durability": "transient",
"environment": "#myenv",
},
],
"environments": [
{
"name": "myenv",
"extends": "realm",
}
],
}),
output = fdecl::Component {
collections: Some(vec![
fdecl::Collection {
name: Some("modular".to_string()),
durability: Some(fdecl::Durability::Persistent),
environment: None,
allowed_offers: None,
..fdecl::Collection::EMPTY
},
fdecl::Collection {
name: Some("tests".to_string()),
durability: Some(fdecl::Durability::Transient),
environment: Some("myenv".to_string()),
allowed_offers: None,
..fdecl::Collection::EMPTY
}
]),
environments: Some(vec![
fdecl::Environment {
name: Some("myenv".to_string()),
extends: Some(fdecl::EnvironmentExtends::Realm),
runners: None,
resolvers: None,
stop_timeout_ms: None,
..fdecl::Environment::EMPTY
}
]),
..default_component_decl()
},
},
test_compile_capabilities => {
input = json!({
"capabilities": [
{
"protocol": "myprotocol",
"path": "/protocol",
},
{
"protocol": "myprotocol2",
},
{
"protocol": [ "myprotocol3", "myprotocol4" ],
},
{
"directory": "mydirectory",
"path": "/directory",
"rights": [ "connect" ],
},
{
"storage": "mystorage",
"backing_dir": "storage",
"from": "#minfs",
"storage_id": "static_instance_id_or_moniker",
},
{
"storage": "mystorage2",
"backing_dir": "storage2",
"from": "#minfs",
"storage_id": "static_instance_id",
},
{
"runner": "myrunner",
"path": "/runner",
},
{
"resolver": "myresolver",
"path": "/resolver"
},
],
"children": [
{
"name": "minfs",
"url": "fuchsia-pkg://fuchsia.com/minfs/stable#meta/minfs.cm",
},
]
}),
output = fdecl::Component {
capabilities: Some(vec![
fdecl::Capability::Protocol (
fdecl::Protocol {
name: Some("myprotocol".to_string()),
source_path: Some("/protocol".to_string()),
..fdecl::Protocol::EMPTY
}
),
fdecl::Capability::Protocol (
fdecl::Protocol {
name: Some("myprotocol2".to_string()),
source_path: Some("/svc/myprotocol2".to_string()),
..fdecl::Protocol::EMPTY
}
),
fdecl::Capability::Protocol (
fdecl::Protocol {
name: Some("myprotocol3".to_string()),
source_path: Some("/svc/myprotocol3".to_string()),
..fdecl::Protocol::EMPTY
}
),
fdecl::Capability::Protocol (
fdecl::Protocol {
name: Some("myprotocol4".to_string()),
source_path: Some("/svc/myprotocol4".to_string()),
..fdecl::Protocol::EMPTY
}
),
fdecl::Capability::Directory (
fdecl::Directory {
name: Some("mydirectory".to_string()),
source_path: Some("/directory".to_string()),
rights: Some(fio::Operations::CONNECT),
..fdecl::Directory::EMPTY
}
),
fdecl::Capability::Storage (
fdecl::Storage {
name: Some("mystorage".to_string()),
source: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "minfs".to_string(),
collection: None,
})),
backing_dir: Some("storage".to_string()),
subdir: None,
storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker),
..fdecl::Storage::EMPTY
}
),
fdecl::Capability::Storage (
fdecl::Storage {
name: Some("mystorage2".to_string()),
source: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "minfs".to_string(),
collection: None,
})),
backing_dir: Some("storage2".to_string()),
subdir: None,
storage_id: Some(fdecl::StorageId::StaticInstanceId),
..fdecl::Storage::EMPTY
}
),
fdecl::Capability::Runner (
fdecl::Runner {
name: Some("myrunner".to_string()),
source_path: Some("/runner".to_string()),
..fdecl::Runner::EMPTY
}
),
fdecl::Capability::Resolver (
fdecl::Resolver {
name: Some("myresolver".to_string()),
source_path: Some("/resolver".to_string()),
..fdecl::Resolver::EMPTY
}
)
]),
children: Some(vec![
fdecl::Child {
name: Some("minfs".to_string()),
url: Some("fuchsia-pkg://fuchsia.com/minfs/stable#meta/minfs.cm".to_string()),
startup: Some(fdecl::StartupMode::Lazy),
environment: None,
on_terminate: None,
..fdecl::Child::EMPTY
}
]),
..default_component_decl()
},
},
test_compile_facets => {
input = json!({
"facets": {
"title": "foo",
"authors": [ "me", "you" ],
"year": "2018",
"metadata": {
"publisher": "The Books Publisher",
}
}
}),
output = fdecl::Component {
facets: Some(fdata::Dictionary {
entries: Some(vec![
fdata::DictionaryEntry {
key: "authors".to_string(),
value: Some(Box::new(fdata::DictionaryValue::StrVec(vec!["me".to_owned(), "you".to_owned()]))),
},
fdata::DictionaryEntry {
key: "metadata.publisher".to_string(),
value: Some(Box::new(fdata::DictionaryValue::Str("The Books Publisher".to_string()))),
},
fdata::DictionaryEntry {
key: "title".to_string(),
value: Some(Box::new(fdata::DictionaryValue::Str("foo".to_string()))),
},
fdata::DictionaryEntry {
key: "year".to_string(),
value: Some(Box::new(fdata::DictionaryValue::Str("2018".to_string()))),
},
]),
..fdata::Dictionary::EMPTY
}
),
..default_component_decl()
},
},
test_compile_environment => {
input = json!({
"environments": [
{
"name": "myenv",
},
{
"name": "myenv2",
"extends": "realm",
},
{
"name": "myenv3",
"extends": "none",
"__stop_timeout_ms": 8000u32,
}
],
}),
output = fdecl::Component {
environments: Some(vec![
fdecl::Environment {
name: Some("myenv".to_string()),
extends: Some(fdecl::EnvironmentExtends::None),
runners: None,
resolvers: None,
stop_timeout_ms: None,
..fdecl::Environment::EMPTY
},
fdecl::Environment {
name: Some("myenv2".to_string()),
extends: Some(fdecl::EnvironmentExtends::Realm),
runners: None,
resolvers: None,
stop_timeout_ms: None,
..fdecl::Environment::EMPTY
},
fdecl::Environment {
name: Some("myenv3".to_string()),
extends: Some(fdecl::EnvironmentExtends::None),
runners: None,
resolvers: None,
stop_timeout_ms: Some(8000),
..fdecl::Environment::EMPTY
},
]),
..default_component_decl()
},
},
test_compile_environment_with_runner_and_resolver => {
input = json!({
"environments": [
{
"name": "myenv",
"runners": [
{
"runner": "dart",
"from": "parent",
}
],
"resolvers": [
{
"resolver": "pkg_resolver",
"from": "parent",
"scheme": "fuchsia-pkg",
}
],
},
],
}),
output = fdecl::Component {
environments: Some(vec![
fdecl::Environment {
name: Some("myenv".to_string()),
extends: Some(fdecl::EnvironmentExtends::None),
runners: Some(vec![
fdecl::RunnerRegistration {
source_name: Some("dart".to_string()),
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
target_name: Some("dart".to_string()),
..fdecl::RunnerRegistration::EMPTY
}
]),
resolvers: Some(vec![
fdecl::ResolverRegistration {
resolver: Some("pkg_resolver".to_string()),
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
scheme: Some("fuchsia-pkg".to_string()),
..fdecl::ResolverRegistration::EMPTY
}
]),
stop_timeout_ms: None,
..fdecl::Environment::EMPTY
},
]),
..default_component_decl()
},
},
test_compile_environment_with_runner_alias => {
input = json!({
"environments": [
{
"name": "myenv",
"runners": [
{
"runner": "dart",
"from": "parent",
"as": "my-dart",
}
],
},
],
}),
output = fdecl::Component {
environments: Some(vec![
fdecl::Environment {
name: Some("myenv".to_string()),
extends: Some(fdecl::EnvironmentExtends::None),
runners: Some(vec![
fdecl::RunnerRegistration {
source_name: Some("dart".to_string()),
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
target_name: Some("my-dart".to_string()),
..fdecl::RunnerRegistration::EMPTY
}
]),
resolvers: None,
stop_timeout_ms: None,
..fdecl::Environment::EMPTY
},
]),
..default_component_decl()
},
},
test_compile_environment_with_debug => {
input = json!({
"capabilities": [
{
"protocol": "fuchsia.serve.service",
},
],
"environments": [
{
"name": "myenv",
"debug": [
{
"protocol": "fuchsia.serve.service",
"from": "self",
"as": "my-service",
}
],
},
],
}),
output = fdecl::Component {
capabilities: Some(vec![
fdecl::Capability::Protocol(
fdecl::Protocol {
name : Some("fuchsia.serve.service".to_owned()),
source_path: Some("/svc/fuchsia.serve.service".to_owned()),
..fdecl::Protocol::EMPTY
}
)
]),
environments: Some(vec![
fdecl::Environment {
name: Some("myenv".to_string()),
extends: Some(fdecl::EnvironmentExtends::None),
debug_capabilities: Some(vec![
fdecl::DebugRegistration::Protocol( fdecl::DebugProtocolRegistration {
source_name: Some("fuchsia.serve.service".to_string()),
source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
target_name: Some("my-service".to_string()),
..fdecl::DebugProtocolRegistration::EMPTY
}),
]),
resolvers: None,
runners: None,
stop_timeout_ms: None,
..fdecl::Environment::EMPTY
},
]),
..default_component_decl()
},
},
test_compile_all_sections => {
input = json!({
"program": {
"runner": "elf",
"binary": "bin/app",
},
"use": [
{ "protocol": "LegacyCoolFonts", "path": "/svc/fuchsia.fonts.LegacyProvider" },
{ "protocol": [ "ReallyGoodFonts", "IWouldNeverUseTheseFonts"]},
{ "protocol": "DebugProtocol", "from": "debug"},
],
"expose": [
{ "directory": "blobfs", "from": "self", "rights": ["r*"]},
],
"offer": [
{
"protocol": "fuchsia.logger.LegacyLog",
"from": "#logger",
"to": [ "#netstack", "#modular" ],
"dependency": "weak"
},
],
"children": [
{
"name": "logger",
"url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
},
{
"name": "netstack",
"url": "fuchsia-pkg://fuchsia.com/netstack/stable#meta/netstack.cm",
},
],
"collections": [
{
"name": "modular",
"durability": "persistent",
},
],
"capabilities": [
{
"directory": "blobfs",
"path": "/volumes/blobfs",
"rights": [ "r*" ],
},
{
"runner": "myrunner",
"path": "/runner",
},
{
"protocol": "fuchsia.serve.service",
}
],
"facets": {
"author": "Fuchsia",
"year": "2018",
},
"environments": [
{
"name": "myenv",
"extends": "realm",
"debug": [
{
"protocol": "fuchsia.serve.service",
"from": "self",
"as": "my-service",
},
{
"protocol": "fuchsia.logger.LegacyLog",
"from": "#logger",
}
]
}
],
}),
output = fdecl::Component {
program: Some(fdecl::Program {
runner: Some("elf".to_string()),
info: Some(fdata::Dictionary {
entries: Some(vec![fdata::DictionaryEntry {
key: "binary".to_string(),
value: Some(Box::new(fdata::DictionaryValue::Str("bin/app".to_string()))),
}]),
..fdata::Dictionary::EMPTY
}),
..fdecl::Program::EMPTY
}),
uses: Some(vec![
fdecl::Use::Protocol (
fdecl::UseProtocol {
dependency_type: Some(fdecl::DependencyType::Strong),
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
source_name: Some("LegacyCoolFonts".to_string()),
target_path: Some("/svc/fuchsia.fonts.LegacyProvider".to_string()),
availability: Some(fdecl::Availability::Required),
..fdecl::UseProtocol::EMPTY
}
),
fdecl::Use::Protocol (
fdecl::UseProtocol {
dependency_type: Some(fdecl::DependencyType::Strong),
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
source_name: Some("ReallyGoodFonts".to_string()),
target_path: Some("/svc/ReallyGoodFonts".to_string()),
availability: Some(fdecl::Availability::Required),
..fdecl::UseProtocol::EMPTY
}
),
fdecl::Use::Protocol (
fdecl::UseProtocol {
dependency_type: Some(fdecl::DependencyType::Strong),
source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
source_name: Some("IWouldNeverUseTheseFonts".to_string()),
target_path: Some("/svc/IWouldNeverUseTheseFonts".to_string()),
availability: Some(fdecl::Availability::Required),
..fdecl::UseProtocol::EMPTY
}
),
fdecl::Use::Protocol (
fdecl::UseProtocol {
dependency_type: Some(fdecl::DependencyType::Strong),
source: Some(fdecl::Ref::Debug(fdecl::DebugRef {})),
source_name: Some("DebugProtocol".to_string()),
target_path: Some("/svc/DebugProtocol".to_string()),
availability: Some(fdecl::Availability::Required),
..fdecl::UseProtocol::EMPTY
}
),
]),
exposes: Some(vec![
fdecl::Expose::Directory (
fdecl::ExposeDirectory {
source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
source_name: Some("blobfs".to_string()),
target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})),
target_name: Some("blobfs".to_string()),
rights: Some(
fio::Operations::CONNECT | fio::Operations::ENUMERATE |
fio::Operations::TRAVERSE | fio::Operations::READ_BYTES |
fio::Operations::GET_ATTRIBUTES
),
subdir: None,
..fdecl::ExposeDirectory::EMPTY
}
),
]),
offers: Some(vec![
fdecl::Offer::Protocol (
fdecl::OfferProtocol {
source: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("fuchsia.logger.LegacyLog".to_string()),
target: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "netstack".to_string(),
collection: None,
})),
target_name: Some("fuchsia.logger.LegacyLog".to_string()),
dependency_type: Some(fdecl::DependencyType::Weak),
availability: Some(fdecl::Availability::Required),
..fdecl::OfferProtocol::EMPTY
}
),
fdecl::Offer::Protocol (
fdecl::OfferProtocol {
source: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("fuchsia.logger.LegacyLog".to_string()),
target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
name: "modular".to_string(),
})),
target_name: Some("fuchsia.logger.LegacyLog".to_string()),
dependency_type: Some(fdecl::DependencyType::Weak),
availability: Some(fdecl::Availability::Required),
..fdecl::OfferProtocol::EMPTY
}
),
]),
capabilities: Some(vec![
fdecl::Capability::Directory (
fdecl::Directory {
name: Some("blobfs".to_string()),
source_path: Some("/volumes/blobfs".to_string()),
rights: Some(fio::Operations::CONNECT | fio::Operations::ENUMERATE |
fio::Operations::TRAVERSE | fio::Operations::READ_BYTES |
fio::Operations::GET_ATTRIBUTES
),
..fdecl::Directory::EMPTY
}
),
fdecl::Capability::Runner (
fdecl::Runner {
name: Some("myrunner".to_string()),
source_path: Some("/runner".to_string()),
..fdecl::Runner::EMPTY
}
),
fdecl::Capability::Protocol(
fdecl::Protocol {
name : Some("fuchsia.serve.service".to_owned()),
source_path: Some("/svc/fuchsia.serve.service".to_owned()),
..fdecl::Protocol::EMPTY
}
)
]),
children: Some(vec![
fdecl::Child {
name: Some("logger".to_string()),
url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
startup: Some(fdecl::StartupMode::Lazy),
environment: None,
on_terminate: None,
..fdecl::Child::EMPTY
},
fdecl::Child {
name: Some("netstack".to_string()),
url: Some("fuchsia-pkg://fuchsia.com/netstack/stable#meta/netstack.cm".to_string()),
startup: Some(fdecl::StartupMode::Lazy),
environment: None,
on_terminate: None,
..fdecl::Child::EMPTY
},
]),
collections: Some(vec![
fdecl::Collection {
name: Some("modular".to_string()),
durability: Some(fdecl::Durability::Persistent),
environment: None,
allowed_offers: None,
..fdecl::Collection::EMPTY
}
]),
environments: Some(vec![
fdecl::Environment {
name: Some("myenv".to_string()),
extends: Some(fdecl::EnvironmentExtends::Realm),
runners: None,
resolvers: None,
stop_timeout_ms: None,
debug_capabilities: Some(vec![
fdecl::DebugRegistration::Protocol( fdecl::DebugProtocolRegistration {
source_name: Some("fuchsia.serve.service".to_string()),
source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
target_name: Some("my-service".to_string()),
..fdecl::DebugProtocolRegistration::EMPTY
}),
fdecl::DebugRegistration::Protocol( fdecl::DebugProtocolRegistration {
source_name: Some("fuchsia.logger.LegacyLog".to_string()),
source: Some(fdecl::Ref::Child(fdecl::ChildRef {
name: "logger".to_string(),
collection: None,
})),
target_name: Some("fuchsia.logger.LegacyLog".to_string()),
..fdecl::DebugProtocolRegistration::EMPTY
}),
]),
..fdecl::Environment::EMPTY
}
]),
facets: Some(fdata::Dictionary {
entries: Some(vec![
fdata::DictionaryEntry {
key: "author".to_string(),
value: Some(Box::new(fdata::DictionaryValue::Str("Fuchsia".to_string()))),
},
fdata::DictionaryEntry {
key: "year".to_string(),
value: Some(Box::new(fdata::DictionaryValue::Str("2018".to_string()))),
},
]),
..fdata::Dictionary::EMPTY
}),
..fdecl::Component::EMPTY
},
},
}
}