blob: 94dba352e2c51d1caf28c6cc9e483d64b2b51225 [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.
//! A library of common utilities used by `cmc` and related tools.
// TODO(fxb/87635): Fix this.
// This crate doesn't comply with all 2018 idioms
#![allow(elided_lifetimes_in_paths)]
pub mod error;
pub mod one_or_many;
#[allow(unused)] // A test-only macro is defined outside of a test builds.
pub mod translate;
use {
crate::error::Error,
cml_macro::{CheckedVec, OneOrMany, Reference},
fidl_fuchsia_io as fio,
json5format::{FormatOptions, PathOption},
lazy_static::lazy_static,
maplit::{hashmap, hashset},
reference_doc::ReferenceDoc,
serde::{de, Deserialize, Serialize},
serde_json::{Map, Value},
std::{
collections::{BTreeMap, HashMap, HashSet},
fmt,
hash::Hash,
num::NonZeroU32,
ops::Deref,
path,
str::FromStr,
},
};
pub use cm_types::{
AllowedOffers, Availability, DependencyType, Durability, Name, OnTerminate, ParseError, Path,
RelativePath, StartupMode, StorageId, Url,
};
pub use crate::{one_or_many::OneOrMany, translate::compile};
lazy_static! {
static ref DEFAULT_EVENT_STREAM_NAME: Name = "EventStream".parse().unwrap();
}
/// A name/identity of a capability exposed/offered to another component.
///
/// Exposed or offered capabilities have an identifier whose format
/// depends on the capability type. For directories and services this is
/// a path, while for storage this is a storage name. Paths and storage
/// names, however, are in different conceptual namespaces, and can't
/// collide with each other.
///
/// This enum allows such names to be specified disambuating what
/// namespace they are in.
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum CapabilityId {
Service(Name),
Protocol(Name),
Directory(Name),
// A service in a `use` declaration has a target path in the component's namespace.
UsedService(Path),
// A protocol in a `use` declaration has a target path in the component's namespace.
UsedProtocol(Path),
// A directory in a `use` declaration has a target path in the component's namespace.
UsedDirectory(Path),
// A storage in a `use` declaration has a target path in the component's namespace.
UsedStorage(Path),
Storage(Name),
Runner(Name),
Resolver(Name),
Event(Name),
EventStreamDeprecated(Name),
EventStream(Path),
}
/// Generates a `Vec<Name>` -> `Vec<CapabilityId>` conversion function.
macro_rules! capability_ids_from_names {
($name:ident, $variant:expr) => {
fn $name(names: Vec<Name>) -> Vec<Self> {
names.into_iter().map(|n| $variant(n)).collect()
}
};
}
/// Generates a `Vec<Path>` -> `Vec<CapabilityId>` conversion function.
macro_rules! capability_ids_from_paths {
($name:ident, $variant:expr) => {
fn $name(paths: Vec<Path>) -> Vec<Self> {
paths.into_iter().map(|p| $variant(p)).collect()
}
};
}
impl CapabilityId {
/// Human readable description of this capability type.
pub fn type_str(&self) -> &'static str {
match self {
CapabilityId::Service(_) => "service",
CapabilityId::Protocol(_) => "protocol",
CapabilityId::Directory(_) => "directory",
CapabilityId::UsedService(_) => "service",
CapabilityId::UsedProtocol(_) => "protocol",
CapabilityId::UsedDirectory(_) => "directory",
CapabilityId::UsedStorage(_) => "storage",
CapabilityId::Storage(_) => "storage",
CapabilityId::Runner(_) => "runner",
CapabilityId::Resolver(_) => "resolver",
CapabilityId::Event(_) => "event",
CapabilityId::EventStreamDeprecated(_) => "event_stream_deprecated",
CapabilityId::EventStream(_) => "event_stream",
}
}
/// Return the directory containing the capability.
pub fn get_dir_path(&self) -> Option<&path::Path> {
match self {
CapabilityId::UsedService(p) => path::Path::new(p.as_str()).parent(),
CapabilityId::UsedProtocol(p) => path::Path::new(p.as_str()).parent(),
CapabilityId::UsedDirectory(p) => Some(path::Path::new(p.as_str())),
CapabilityId::UsedStorage(p) => Some(path::Path::new(p.as_str())),
CapabilityId::EventStream(p) => path::Path::new(p.as_str()).parent(),
_ => None,
}
}
/// Given a Use clause, return the set of target identifiers.
///
/// When only one capability identifier is specified, the target identifier name is derived
/// using the "path" clause. If a "path" clause is not specified, the target identifier is the
/// same name as the source.
///
/// When multiple capability identifiers are specified, the target names are the same as the
/// source names.
pub fn from_use(use_: &Use) -> Result<Vec<CapabilityId>, Error> {
// TODO: Validate that exactly one of these is set.
let alias = use_.path.as_ref();
if let Some(n) = use_.service() {
return Ok(Self::used_services_from(Self::get_one_or_many_svc_paths(
n,
alias,
use_.capability_type(),
)?));
} else if let Some(n) = use_.protocol() {
return Ok(Self::used_protocols_from(Self::get_one_or_many_svc_paths(
n,
alias,
use_.capability_type(),
)?));
} else if let Some(_) = use_.directory.as_ref() {
if use_.path.is_none() {
return Err(Error::validate("\"path\" should be present for `use directory`."));
}
return Ok(vec![CapabilityId::UsedDirectory(use_.path.as_ref().unwrap().clone())]);
} else if let Some(_) = use_.storage.as_ref() {
if use_.path.is_none() {
return Err(Error::validate("\"path\" should be present for `use storage`."));
}
return Ok(vec![CapabilityId::UsedStorage(use_.path.as_ref().unwrap().clone())]);
} else if let Some(OneOrMany::One(n)) = use_.event.as_ref() {
return Ok(vec![CapabilityId::Event(alias_or_name(use_.r#as.as_ref(), n))]);
} else if let Some(OneOrMany::Many(events)) = use_.event.as_ref() {
return match (use_.r#as.as_ref(), use_.filter.as_ref(), events.len()) {
(Some(alias), _, 1) => Ok(vec![CapabilityId::Event(alias.clone())]),
(None, Some(_), 1) => Ok(vec![CapabilityId::Event(events[0].clone())]),
(Some(_), None, _) => Err(Error::validate(
"\"as\" can only be specified when one `event` is supplied",
)),
(None, Some(_), _) => Err(Error::validate(
"\"filter\" can only be specified when one `event` is supplied",
)),
(Some(_), Some(_), _) => Err(Error::validate(
"\"as\",\"filter\" can only be specified when one `event` is supplied",
)),
(None, None, _) => Ok(events
.iter()
.map(|event: &Name| CapabilityId::Event(event.clone()))
.collect()),
};
} else if let Some(name) = use_.event_stream_deprecated() {
return Ok(vec![CapabilityId::EventStreamDeprecated(name)]);
} else if let Some(_) = use_.event_stream() {
if let Some(path) = use_.path() {
return Ok(vec![CapabilityId::EventStream(path.clone())]);
}
return Ok(vec![CapabilityId::EventStream(Path::new(
"/svc/fuchsia.component.EventStream".to_string(),
)?)]);
}
// Unsupported capability type.
let supported_keywords = use_
.supported()
.into_iter()
.map(|k| format!("\"{}\"", k))
.collect::<Vec<_>>()
.join(", ");
Err(Error::validate(format!(
"`{}` declaration is missing a capability keyword, one of: {}",
use_.decl_type(),
supported_keywords,
)))
}
pub fn from_capability(capability: &Capability) -> Result<Vec<CapabilityId>, Error> {
// TODO: Validate that exactly one of these is set.
if let Some(n) = capability.service() {
if n.is_many() && capability.path.is_some() {
return Err(Error::validate(
"\"path\" can only be specified when one `service` is supplied.",
));
}
return Ok(Self::services_from(Self::get_one_or_many_names(
n,
None,
capability.capability_type(),
)?));
} else if let Some(n) = capability.protocol() {
if n.is_many() && capability.path.is_some() {
return Err(Error::validate(
"\"path\" can only be specified when one `protocol` is supplied.",
));
}
return Ok(Self::protocols_from(Self::get_one_or_many_names(
n,
None,
capability.capability_type(),
)?));
} else if let Some(n) = capability.directory() {
return Ok(Self::directories_from(Self::get_one_or_many_names(
n,
None,
capability.capability_type(),
)?));
} else if let Some(n) = capability.storage() {
if capability.storage_id.is_none() {
return Err(Error::validate(
"Storage declaration is missing \"storage_id\", but is required.",
));
}
return Ok(Self::storages_from(Self::get_one_or_many_names(
n,
None,
capability.capability_type(),
)?));
} else if let Some(n) = capability.runner() {
return Ok(Self::runners_from(Self::get_one_or_many_names(
n,
None,
capability.capability_type(),
)?));
} else if let Some(n) = capability.resolver() {
return Ok(Self::resolvers_from(Self::get_one_or_many_names(
n,
None,
capability.capability_type(),
)?));
} else if let Some(n) = capability.event() {
return Ok(Self::events_from(Self::get_one_or_many_names(
n,
None,
capability.capability_type(),
)?));
} else if let Some(n) = capability.event_stream() {
return Ok(Self::events_from(Self::get_one_or_many_names(
n,
None,
capability.capability_type(),
)?));
}
// Unsupported capability type.
let supported_keywords = capability
.supported()
.into_iter()
.map(|k| format!("\"{}\"", k))
.collect::<Vec<_>>()
.join(", ");
Err(Error::validate(format!(
"`{}` declaration is missing a capability keyword, one of: {}",
capability.decl_type(),
supported_keywords,
)))
}
/// Given an Offer or Expose clause, return the set of target identifiers.
///
/// When only one capability identifier is specified, the target identifier name is derived
/// using the "as" clause. If an "as" clause is not specified, the target identifier is the
/// same name as the source.
///
/// When multiple capability identifiers are specified, the target names are the same as the
/// source names.
pub fn from_offer_expose<T>(clause: &T) -> Result<Vec<CapabilityId>, Error>
where
T: CapabilityClause + AsClause + FilterClause + fmt::Debug,
{
// TODO: Validate that exactly one of these is set.
let alias = clause.r#as();
if let Some(n) = clause.service() {
return Ok(Self::services_from(Self::get_one_or_many_names(
n,
alias,
clause.capability_type(),
)?));
} else if let Some(n) = clause.protocol() {
return Ok(Self::protocols_from(Self::get_one_or_many_names(
n,
alias,
clause.capability_type(),
)?));
} else if let Some(n) = clause.directory() {
return Ok(Self::directories_from(Self::get_one_or_many_names(
n,
alias,
clause.capability_type(),
)?));
} else if let Some(n) = clause.storage() {
return Ok(Self::storages_from(Self::get_one_or_many_names(
n,
alias,
clause.capability_type(),
)?));
} else if let Some(n) = clause.runner() {
return Ok(Self::runners_from(Self::get_one_or_many_names(
n,
alias,
clause.capability_type(),
)?));
} else if let Some(n) = clause.resolver() {
return Ok(Self::resolvers_from(Self::get_one_or_many_names(
n,
alias,
clause.capability_type(),
)?));
} else if let Some(events) = clause.event() {
return match events {
event @ OneOrMany::One(_) => Ok(Self::events_from(Self::get_one_or_many_names(
event,
alias,
clause.capability_type(),
)?)),
OneOrMany::Many(events) => match (alias, clause.filter(), events.len()) {
(Some(alias), _, 1) => Ok(vec![CapabilityId::Event(alias.clone())]),
(None, Some(_), 1) => Ok(vec![CapabilityId::Event(events[0].clone())]),
(Some(_), None, _) => Err(Error::validate(
"\"as\" can only be specified when one `event` is supplied",
)),
(None, Some(_), _) => Err(Error::validate(
"\"filter\" can only be specified when one `event` is supplied",
)),
(Some(_), Some(_), _) => Err(Error::validate(
"\"as\",\"filter\" can only be specified when one `event` is supplied",
)),
(None, None, _) => Ok(events
.iter()
.map(|event: &Name| CapabilityId::Event(event.clone()))
.collect()),
},
};
} else if let Some(event_stream) = clause.event_stream() {
return Ok(Self::events_from(Self::get_one_or_many_names(
event_stream,
alias,
clause.capability_type(),
)?));
}
// Unsupported capability type.
let supported_keywords = clause
.supported()
.into_iter()
.map(|k| format!("\"{}\"", k))
.collect::<Vec<_>>()
.join(", ");
Err(Error::validate(format!(
"`{}` declaration is missing a capability keyword, one of: {}",
clause.decl_type(),
supported_keywords,
)))
}
/// Returns the target names as a `Vec` from a declaration with `names` and `alias` as a `Vec`.
fn get_one_or_many_names(
names: OneOrMany<Name>,
alias: Option<&Name>,
capability_type: &str,
) -> Result<Vec<Name>, Error> {
let names: Vec<Name> = names.into_iter().collect();
if names.len() == 1 {
Ok(vec![alias_or_name(alias, &names[0])])
} else {
if alias.is_some() {
return Err(Error::validate(format!(
"\"as\" can only be specified when one `{}` is supplied.",
capability_type,
)));
}
Ok(names)
}
}
/// Returns the target paths as a `Vec` from a `use` declaration with `names` and `alias`.
fn get_one_or_many_svc_paths(
names: OneOrMany<Name>,
alias: Option<&Path>,
capability_type: &str,
) -> Result<Vec<Path>, Error> {
let names: Vec<Name> = names.into_iter().collect();
match (names.len(), alias) {
(_, None) => {
Ok(names.into_iter().map(|n| format!("/svc/{}", n).parse().unwrap()).collect())
}
(1, Some(alias)) => Ok(vec![alias.clone()]),
(_, Some(_)) => {
return Err(Error::validate(format!(
"\"path\" can only be specified when one `{}` is supplied.",
capability_type,
)));
}
}
}
capability_ids_from_names!(services_from, CapabilityId::Service);
capability_ids_from_names!(protocols_from, CapabilityId::Protocol);
capability_ids_from_names!(directories_from, CapabilityId::Directory);
capability_ids_from_names!(storages_from, CapabilityId::Storage);
capability_ids_from_names!(runners_from, CapabilityId::Runner);
capability_ids_from_names!(resolvers_from, CapabilityId::Resolver);
capability_ids_from_names!(events_from, CapabilityId::Event);
capability_ids_from_paths!(used_services_from, CapabilityId::UsedService);
capability_ids_from_paths!(used_protocols_from, CapabilityId::UsedProtocol);
}
impl fmt::Display for CapabilityId {
/// Return the string ID of this clause.
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
CapabilityId::Service(n)
| CapabilityId::Storage(n)
| CapabilityId::Runner(n)
| CapabilityId::Resolver(n)
| CapabilityId::Event(n) => n.as_str(),
CapabilityId::UsedService(p)
| CapabilityId::UsedProtocol(p)
| CapabilityId::UsedDirectory(p)
| CapabilityId::UsedStorage(p) => p.as_str(),
CapabilityId::EventStreamDeprecated(p) => p.as_str(),
CapabilityId::EventStream(p) => p.as_str(),
CapabilityId::Protocol(p) | CapabilityId::Directory(p) => p.as_str(),
};
write!(f, "{}", s)
}
}
/// A list of rights.
#[derive(CheckedVec, Debug, PartialEq, Clone)]
#[checked_vec(
expected = "a nonempty array of rights, with unique elements",
min_length = 1,
unique_items = true
)]
pub struct Rights(pub Vec<Right>);
/// Generates deserializer for `OneOrMany<Name>`.
#[derive(OneOrMany, Debug, Clone)]
#[one_or_many(
expected = "a name or nonempty array of names, with unique elements",
inner_type = "Name",
min_length = 1,
unique_items = true
)]
pub struct OneOrManyNames;
/// Generates deserializer for `OneOrMany<Path>`.
#[derive(OneOrMany, Debug, Clone)]
#[one_or_many(
expected = "a path or nonempty array of paths, with unique elements",
inner_type = "Path",
min_length = 1,
unique_items = true
)]
pub struct OneOrManyPaths;
/// Generates deserializer for `OneOrMany<ExposeFromRef>`.
#[derive(OneOrMany, Debug, Clone)]
#[one_or_many(
expected = "one or an array of \"framework\", \"self\", or \"#<child-name>\"",
inner_type = "ExposeFromRef",
min_length = 1,
unique_items = true
)]
pub struct OneOrManyExposeFromRefs;
/// Generates deserializer for `OneOrMany<OfferToRef>`.
#[derive(OneOrMany, Debug, Clone)]
#[one_or_many(
expected = "one or an array of \"#<child-name>\" or \"#<collection-name>\", with unique elements",
inner_type = "OfferToRef",
min_length = 1,
unique_items = true
)]
pub struct OneOrManyOfferToRefs;
/// Generates deserializer for `OneOrMany<OfferFromRef>`.
#[derive(OneOrMany, Debug, Clone)]
#[one_or_many(
expected = "one or an array of \"parent\", \"framework\", \"self\", or \"#<child-name>\"",
inner_type = "OfferFromRef",
min_length = 1,
unique_items = true
)]
pub struct OneOrManyOfferFromRefs;
/// Generates deserializer for `OneOrMany<UseFromRef>`.
#[derive(OneOrMany, Debug, Clone)]
#[one_or_many(
expected = "one or an array of \"#<collection-name>\", or \"#<child-name>\"",
inner_type = "EventScope",
min_length = 1,
unique_items = true
)]
pub struct OneOrManyEventScope;
/// The stop timeout configured in an environment.
#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
pub struct StopTimeoutMs(pub u32);
impl<'de> de::Deserialize<'de> for StopTimeoutMs {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
struct Visitor;
impl<'de> de::Visitor<'de> for Visitor {
type Value = StopTimeoutMs;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("an unsigned 32-bit integer")
}
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
where
E: de::Error,
{
if v < 0 || v > i64::from(u32::max_value()) {
return Err(E::invalid_value(
de::Unexpected::Signed(v),
&"an unsigned 32-bit integer",
));
}
Ok(StopTimeoutMs(v as u32))
}
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
where
E: de::Error,
{
self.visit_i64(value as i64)
}
}
deserializer.deserialize_i64(Visitor)
}
}
/// A relative reference to another object. This is a generic type that can encode any supported
/// reference subtype. For named references, it holds a reference to the name instead of the name
/// itself.
///
/// Objects of this type are usually derived from conversions of context-specific reference
/// types that `#[derive(Reference)]`. This type makes it easy to write helper functions that operate on
/// generic references.
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum AnyRef<'a> {
/// A named reference. Parsed as `#name`.
Named(&'a Name),
/// A reference to the parent. Parsed as `parent`.
Parent,
/// A reference to the framework (component manager). Parsed as `framework`.
Framework,
/// A reference to the debug. Parsed as `debug`.
Debug,
/// A reference to this component. Parsed as `self`.
Self_,
/// An intentionally omitted reference.
Void,
}
/// Format an `AnyRef` as a string.
impl fmt::Display for AnyRef<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Named(name) => write!(f, "#{}", name),
Self::Parent => write!(f, "parent"),
Self::Framework => write!(f, "framework"),
Self::Debug => write!(f, "debug"),
Self::Self_ => write!(f, "self"),
Self::Void => write!(f, "void"),
}
}
}
/// A reference in a `use from`.
#[derive(Debug, PartialEq, Eq, Hash, Clone, Reference)]
#[reference(
expected = "\"parent\", \"framework\", \"debug\", \"self\", \"#<capability-name>\", \"#<child-name>\", or none"
)]
pub enum UseFromRef {
/// A reference to the parent.
Parent,
/// A reference to the framework.
Framework,
/// A reference to debug.
Debug,
/// A reference to a child or a capability declared on self.
///
/// A reference to a capability is only valid on use declarations for a protocol that
/// references a storage capability declared in the same component, which will cause the
/// framework to host a fuchsia.sys2.StorageAdmin protocol for the component.
///
/// This cannot be used to directly access capabilities that a component itself declares.
Named(Name),
/// A reference to this component.
Self_,
}
/// The scope of an event.
#[derive(Debug, PartialEq, Eq, Hash, Clone, Reference)]
#[reference(expected = "\"#<collection-name>\", \"#<child-name>\", or none")]
pub enum EventScope {
/// A reference to a child or a collection.
Named(Name),
}
/// A reference in an `expose from`.
#[derive(Debug, PartialEq, Eq, Hash, Clone, Reference)]
#[reference(expected = "\"framework\", \"self\", or \"#<child-name>\"")]
pub enum ExposeFromRef {
/// A reference to a child or collection.
Named(Name),
/// A reference to the framework.
Framework,
/// A reference to this component.
Self_,
}
/// A reference in an `expose to`.
#[derive(Debug, PartialEq, Eq, Hash, Clone, Reference)]
#[reference(expected = "\"parent\", \"framework\", or none")]
pub enum ExposeToRef {
/// A reference to the parent.
Parent,
/// A reference to the framework.
Framework,
}
/// A reference in an `offer from`.
#[derive(Debug, PartialEq, Eq, Hash, Clone, Reference)]
#[reference(expected = "\"parent\", \"framework\", \"self\", \"void\", or \"#<child-name>\"")]
pub enum OfferFromRef {
/// A reference to a child or collection.
Named(Name),
/// A reference to the parent.
Parent,
/// A reference to the framework.
Framework,
/// A reference to this component.
Self_,
/// An intentionally omitted source.
Void,
}
impl OfferFromRef {
pub fn is_named(&self) -> bool {
match self {
OfferFromRef::Named(_) => true,
_ => false,
}
}
}
/// A reference in an `offer to`.
#[derive(Debug, PartialEq, Eq, Hash, Clone, Reference)]
#[reference(expected = "\"#<child-name>\" or \"#<collection-name>\"")]
pub enum OfferToRef {
/// A reference to a child or collection.
Named(Name),
}
/// A reference in an `offer to`.
#[derive(Debug, Deserialize, PartialEq, Eq, Hash, Clone, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum SourceAvailability {
Required,
Unknown,
}
impl Default for SourceAvailability {
fn default() -> Self {
Self::Required
}
}
/// A reference in an environment.
#[derive(Debug, PartialEq, Eq, Hash, Clone, Reference)]
#[reference(expected = "\"#<environment-name>\"")]
pub enum EnvironmentRef {
/// A reference to an environment defined in this component.
Named(Name),
}
/// A reference in a `storage from`.
#[derive(Debug, PartialEq, Eq, Hash, Clone, Reference)]
#[reference(expected = "\"parent\", \"self\", or \"#<child-name>\"")]
pub enum CapabilityFromRef {
/// A reference to a child.
Named(Name),
/// A reference to the parent.
Parent,
/// A reference to this component.
Self_,
}
/// A reference in an environment registration.
#[derive(Debug, PartialEq, Eq, Hash, Clone, Reference)]
#[reference(expected = "\"parent\", \"self\", or \"#<child-name>\"")]
pub enum RegistrationRef {
/// A reference to a child.
Named(Name),
/// A reference to the parent.
Parent,
/// A reference to this component.
Self_,
}
#[derive(Deserialize, Debug, PartialEq, Clone, Serialize)]
#[serde(deny_unknown_fields)]
pub struct EventSubscription {
pub event: OneOrMany<Name>,
}
/// A right or bundle of rights to apply to a directory.
#[derive(Deserialize, Clone, Debug, Eq, PartialEq, Hash, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Right {
// Individual
Connect,
Enumerate,
Execute,
GetAttributes,
ModifyDirectory,
ReadBytes,
Traverse,
UpdateAttributes,
WriteBytes,
// Aliass
#[serde(rename = "r*")]
ReadAlias,
#[serde(rename = "w*")]
WriteAlias,
#[serde(rename = "x*")]
ExecuteAlias,
#[serde(rename = "rw*")]
ReadWriteAlias,
#[serde(rename = "rx*")]
ReadExecuteAlias,
}
impl fmt::Display for Right {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Self::Connect => "connect",
Self::Enumerate => "enumerate",
Self::Execute => "execute",
Self::GetAttributes => "get_attributes",
Self::ModifyDirectory => "modify_directory",
Self::ReadBytes => "read_bytes",
Self::Traverse => "traverse",
Self::UpdateAttributes => "update_attributes",
Self::WriteBytes => "write_bytes",
Self::ReadAlias => "r*",
Self::WriteAlias => "w*",
Self::ExecuteAlias => "x*",
Self::ReadWriteAlias => "rw*",
Self::ReadExecuteAlias => "rx*",
};
write!(f, "{}", s)
}
}
impl Right {
/// Expands this right or bundle or rights into a list of `fio::Operations`.
pub fn expand(&self) -> Vec<fio::Operations> {
match self {
Self::Connect => vec![fio::Operations::CONNECT],
Self::Enumerate => vec![fio::Operations::ENUMERATE],
Self::Execute => vec![fio::Operations::EXECUTE],
Self::GetAttributes => vec![fio::Operations::GET_ATTRIBUTES],
Self::ModifyDirectory => vec![fio::Operations::MODIFY_DIRECTORY],
Self::ReadBytes => vec![fio::Operations::READ_BYTES],
Self::Traverse => vec![fio::Operations::TRAVERSE],
Self::UpdateAttributes => vec![fio::Operations::UPDATE_ATTRIBUTES],
Self::WriteBytes => vec![fio::Operations::WRITE_BYTES],
Self::ReadAlias => vec![
fio::Operations::CONNECT,
fio::Operations::ENUMERATE,
fio::Operations::TRAVERSE,
fio::Operations::READ_BYTES,
fio::Operations::GET_ATTRIBUTES,
],
Self::WriteAlias => vec![
fio::Operations::CONNECT,
fio::Operations::ENUMERATE,
fio::Operations::TRAVERSE,
fio::Operations::WRITE_BYTES,
fio::Operations::MODIFY_DIRECTORY,
fio::Operations::UPDATE_ATTRIBUTES,
],
Self::ExecuteAlias => vec![
fio::Operations::CONNECT,
fio::Operations::ENUMERATE,
fio::Operations::TRAVERSE,
fio::Operations::EXECUTE,
],
Self::ReadWriteAlias => vec![
fio::Operations::CONNECT,
fio::Operations::ENUMERATE,
fio::Operations::TRAVERSE,
fio::Operations::READ_BYTES,
fio::Operations::WRITE_BYTES,
fio::Operations::MODIFY_DIRECTORY,
fio::Operations::GET_ATTRIBUTES,
fio::Operations::UPDATE_ATTRIBUTES,
],
Self::ReadExecuteAlias => vec![
fio::Operations::CONNECT,
fio::Operations::ENUMERATE,
fio::Operations::TRAVERSE,
fio::Operations::READ_BYTES,
fio::Operations::GET_ATTRIBUTES,
fio::Operations::EXECUTE,
],
}
}
}
/// # Component manifest (`.cml`) reference
///
/// A `.cml` file contains a single json5 object literal with the keys below.
///
/// Where string values are expected, a list of valid values is generally documented.
/// The following string value types are reused and must follow specific rules.
///
/// ## String types
///
/// ### Names {#names}
///
/// Both capabilities and a component's children are named. A name string must consist of one or
/// more of the following characters: `a-z`, `0-9`, `_`, `.`, `-`.
///
/// ### References {#references}
///
/// A reference string takes the form of `#<name>`, where `<name>` refers to the name of a child:
///
/// - A [static child instance][doc-static-children] whose name is
/// `<name>`, or
/// - A [collection][doc-collections] whose name is `<name>`.
///
/// [doc-static-children]: /docs/concepts/components/v2/realms.md#static-children
/// [doc-collections]: /docs/concepts/components/v2/realms.md#collections
/// [doc-protocol]: /docs/concepts/components/v2/capabilities/protocol.md
/// [doc-directory]: /docs/concepts/components/v2/capabilities/directory.md
/// [doc-storage]: /docs/concepts/components/v2/capabilities/storage.md
/// [doc-resolvers]: /docs/concepts/components/v2/capabilities/resolvers.md
/// [doc-runners]: /docs/concepts/components/v2/capabilities/runners.md
/// [doc-event]: /docs/concepts/components/v2/capabilities/event.md
/// [doc-service]: /docs/concepts/components/v2/capabilities/service.md
/// [doc-directory-rights]: /docs/concepts/components/v2/capabilities/directory#directory-capability-rights
///
/// ## Top-level keys
#[derive(ReferenceDoc, Deserialize, Debug, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Document {
/// The optional `include` property describes zero or more other component manifest
/// files to be merged into this component manifest. For example:
///
/// ```json5
/// include: [ "syslog/client.shard.cml" ]
/// ```
///
/// In the example given above, the component manifest is including contents from a
/// manifest shard provided by the `syslog` library, thus ensuring that the
/// component functions correctly at runtime if it attempts to write to `syslog`. By
/// convention such files are called "manifest shards" and end with `.shard.cml`.
///
/// Include paths prepended with `//` are relative to the source root of the Fuchsia
/// checkout. However, include paths not prepended with `//`, as in the example
/// above, are resolved from Fuchsia SDK libraries (`//sdk/lib`) that export
/// component manifest shards.
///
/// For reference, inside the Fuchsia checkout these two include paths are
/// equivalent:
///
/// * `syslog/client.shard.cml`
/// * `//sdk/lib/syslog/client.shard.cml`
///
/// You can review the outcome of merging any and all includes into a component
/// manifest file by invoking the following command:
///
/// Note: The `fx` command below is for developers working in a Fuchsia source
/// checkout environment.
///
/// ```sh
/// fx cmc include {{ "<var>" }}cmx_file{{ "</var>" }} --includeroot $FUCHSIA_DIR --includepath $FUCHSIA_DIR/sdk/lib
/// ```
///
/// Includes are transitive, meaning that shards can have their own includes.
///
/// Include paths can have diamond dependencies. For instance this is valid:
/// A includes B, A includes C, B includes D, C includes D.
/// In this case A will transitively include B, C, D.
///
/// Include paths cannot have cycles. For instance this is invalid:
/// A includes B, B includes A.
/// A cycle such as the above will result in a compile-time error.
#[serde(skip_serializing_if = "Option::is_none")]
pub include: Option<Vec<String>>,
/// Components that are executable include a `program` section. The `program`
/// section must set the `runner` property to select a [runner][doc-runners] to run
/// the component. The format of the rest of the `program` section is determined by
/// that particular runner.
///
/// # ELF runners {#elf-runners}
///
/// If the component uses the ELF runner, `program` must include the following
/// properties, at a minimum:
///
/// - `runner`: must be set to `"elf"`
/// - `binary`: Package-relative path to the executable binary
/// - `args` _(optional)_: List of arguments
///
/// Example:
///
/// ```json5
/// program: {
/// runner: "elf",
/// binary: "bin/hippo",
/// args: [ "Hello", "hippos!" ],
/// },
/// ```
///
/// For a complete list of properties, see: [ELF Runner](/docs/concepts/components/v2/elf_runner.md)
///
/// # Other runners {#other-runners}
///
/// If a component uses a custom runner, values inside the `program` stanza other
/// than `runner` are specific to the runner. The runner receives the arguments as a
/// dictionary of key and value pairs. Refer to the specific runner being used to
/// determine what keys it expects to receive, and how it interprets them.
///
/// [doc-runners]: /docs/concepts/components/v2/capabilities/runners.md
#[reference_doc(json_type = "object")]
#[serde(skip_serializing_if = "Option::is_none")]
pub program: Option<Program>,
/// The `children` section declares child component instances as described in
/// [Child component instances][doc-children].
///
/// [doc-children]: /docs/concepts/components/v2/realms.md#child-component-instances
#[reference_doc(recurse)]
#[serde(skip_serializing_if = "Option::is_none")]
pub children: Option<Vec<Child>>,
/// The `collections` section declares collections as described in
/// [Component collections][doc-collections].
#[reference_doc(recurse)]
#[serde(skip_serializing_if = "Option::is_none")]
pub collections: Option<Vec<Collection>>,
/// The `environments` section declares environments as described in
/// [Environments][doc-environments].
///
/// [doc-environments]: /docs/concepts/components/v2/environments.md
#[reference_doc(recurse)]
#[serde(skip_serializing_if = "Option::is_none")]
pub environments: Option<Vec<Environment>>,
/// The `capabilities` section defines capabilities that are provided by this component.
/// Capabilities that are [offered](#offer) or [exposed](#expose) from `self` must be declared
/// here.
///
/// One and only one of the capability type keys (`protocol`, `directory`, `service`, ...) is required.
///
/// [glossary.outgoing directory]: /docs/glossary/README.md#outgoing-directory
#[reference_doc(recurse)]
#[serde(skip_serializing_if = "Option::is_none")]
pub capabilities: Option<Vec<Capability>>,
/// For executable components, declares capabilities that this
/// component requires in its [namespace][glossary.namespace] at runtime.
/// Capabilities are routed from the `parent` unless otherwise specified,
/// and each capability must have a valid route through all components between
/// this component and the capability's source.
///
/// [fidl-environment-decl]: /reference/fidl/fuchsia.component.decl#Environment
/// [glossary.namespace]: /docs/glossary/README.md#namespace
#[reference_doc(recurse)]
#[serde(skip_serializing_if = "Option::is_none")]
pub r#use: Option<Vec<Use>>,
/// Declares the capabilities that are made available to the parent component or to the
/// framework. It is valid to `expose` from `self` or from a child component.
///
/// One and only one of the capability type keys (`protocol`, `directory`, `service`, ...) is required.
#[reference_doc(recurse)]
#[serde(skip_serializing_if = "Option::is_none")]
pub expose: Option<Vec<Expose>>,
/// Declares the capabilities that are made available to a [child component][doc-children]
/// instance or a [child collection][doc-collections].
#[reference_doc(recurse)]
#[serde(skip_serializing_if = "Option::is_none")]
pub offer: Option<Vec<Offer>>,
/// Contains metadata that components may interpret for their own purposes. The component
/// framework enforces no schema for this section, but third parties may expect their facets to
/// adhere to a particular schema.
#[serde(skip_serializing_if = "Option::is_none")]
pub facets: Option<Map<String, Value>>,
/// The configuration schema as defined by a component. Each key represents a single field
/// in the schema.
///
/// NOTE: This feature is currently experimental and access is controlled through an allowlist
/// in fuchsia.git at `//tools/cmc/build/restricted_features/BUILD.gn`.
///
/// Configuration fields are JSON objects and must define a `type` which can be one of the
/// following strings:
/// `bool`, `uint8`, `int8`, `uint16`, `int16`, `uint32`, `int32`, `uint64`, `int64`,
/// `string`, `vector`
///
/// Example:
///
/// ```json5
/// config: {
/// debug_mode: {
/// type: "bool"
/// },
/// }
/// ```
///
/// Strings must define the `max_size` property as a non-zero integer.
///
/// Example:
///
/// ```json5
/// config: {
/// verbosity: {
/// type: "string",
/// max_size: 20,
/// }
/// }
/// ```
///
/// Vectors must set the `max_count` property as a non-zero integer. Vectors must also set the
/// `element` property as a JSON object which describes the element being contained in the
/// vector. Vectors can contain booleans, integers, and strings but cannot contain other
/// vectors.
///
/// Example:
///
/// ```json5
/// config: {
/// tags: {
/// type: "vector",
/// max_count: 20,
/// element: {
/// type: "string",
/// max_size: 50,
/// }
/// }
/// }
/// ```
#[reference_doc(json_type = "object")]
#[serde(skip_serializing_if = "Option::is_none")]
pub config: Option<BTreeMap<ConfigKey, ConfigValueType>>,
}
macro_rules! merge_from_field {
($self:ident, $other:ident, $field_name:ident) => {
if let Some(ref mut ours) = $self.$field_name {
if let Some(theirs) = $other.$field_name.take() {
// Add their elements, ignoring dupes with ours
for t in theirs {
if !ours.contains(&t) {
ours.push(t);
}
}
}
} else if let Some(theirs) = $other.$field_name.take() {
$self.$field_name.replace(theirs);
}
};
}
macro_rules! flatten_on_capability_type {
($input_clause:ident, $type:ident) => {
match &$input_clause.$type {
Some(OneOrMany::Many(ref clause_vec)) => clause_vec
.iter()
.map(|c| {
let mut clause_clone = $input_clause.clone();
clause_clone.$type = Some(OneOrMany::One(c.clone()));
clause_clone
})
.collect::<Vec<_>>(),
Some(OneOrMany::One(_)) => vec![$input_clause.clone()],
_ => unreachable!("unable to flatten empty capability type"),
}
};
}
// flatten_field expands capability clauses which have multiple specifications
// e.g. a Use clause that has a list of protocols. This is done on both the source
// and destination Document before merging in order to correctly deduplicate capabilities
// specified in both Documents.
macro_rules! flatten_field {
($input_doc:ident, $field_name:ident,$($sub_field: ident),* ) => {
match &$input_doc.$field_name {
Some(in_vals) => {
let mut new_vals = Vec::new();
for curr_val in in_vals {
let val = match curr_val.capability_type() {
$( stringify!($sub_field) => flatten_on_capability_type!(curr_val, $sub_field),)*
_ => vec![curr_val.clone()],
};
new_vals.extend(val);
}
$input_doc.$field_name = Some(new_vals);
},
_ => {}
}
};
}
// flatten_docs updates one or more Documents by expanding fields where one entry
// can represent multiple capabilities. This prepares the Document to be merged with
// the correct deduplication behavior.
macro_rules! flatten_docs {
($($doc: ident),+ ) => {
$({
flatten_field!($doc, r#use, protocol, service, event);
flatten_field!($doc, offer, protocol, service, event);
flatten_field!($doc, expose, protocol, service);
flatten_field!($doc, capabilities, protocol, service);
})+
};
}
impl Document {
pub fn merge_from(
&mut self,
other: &mut Document,
include_path: &path::Path,
) -> Result<(), Error> {
// Flatten the mergable fields that may contain a
// list of capabilities in one clause.
flatten_docs!(self, other);
merge_from_field!(self, other, include);
merge_from_field!(self, other, r#use);
merge_from_field!(self, other, expose);
merge_from_field!(self, other, offer);
merge_from_field!(self, other, capabilities);
merge_from_field!(self, other, children);
merge_from_field!(self, other, collections);
merge_from_field!(self, other, environments);
self.merge_program(other, include_path)?;
self.merge_facets(other, include_path)?;
// TODO(https://fxbug.dev/93679): Multiple config schemas aren't merged together
if self.config.is_some() && other.config.is_some() {
return Err(Error::validate(format!(
"multiple config schemas found (last import: {}). See https://fxbug.dev/93679 for more information",
include_path.display()
)));
} else if let Some(config) = other.config.take() {
self.config.replace(config);
}
Ok(())
}
fn merge_program(
&mut self,
other: &mut Document,
include_path: &path::Path,
) -> Result<(), Error> {
if let None = other.program {
return Ok(());
}
if let None = self.program {
self.program = Some(Program::default());
}
let my_program = self.program.as_mut().unwrap();
let other_program = other.program.as_mut().unwrap();
if let Some(other_runner) = other_program.runner.take() {
my_program.runner = match &my_program.runner {
Some(runner) if *runner != other_runner => {
return Err(Error::validate(format!(
"manifest include had a conflicting `program.runner`: {}",
include_path.display()
)))
}
_ => Some(other_runner),
}
}
for (key, value) in other_program.info.iter() {
if let Some(_) = my_program.info.insert(key.clone(), value.clone()) {
return Err(Error::validate(format!(
"manifest include had a conflicting `program.{}`: {}",
key,
include_path.display()
)));
}
}
Ok(())
}
fn merge_maps(
self_map: &mut Map<String, Value>,
include_map: &Map<String, Value>,
outer_key: &str,
include_path: &path::Path,
) -> Result<(), Error> {
for (key, value) in include_map.iter() {
match self_map.get_mut(key) {
None => {
// Key not present in self map, insert it from include map.
self_map.insert(key.clone(), value.clone());
}
// Self and include maps share the same key
Some(Value::Object(self_nested_map)) => match value {
// The include value is an object and can be recursively merged
Value::Object(include_nested_map) => {
let combined_key = format!("{}.{}", outer_key, key);
// Recursively merge maps
Self::merge_maps(
self_nested_map,
include_nested_map,
&combined_key,
include_path,
)?;
}
_ => {
// Cannot merge object and non-object
return Err(Error::validate(format!(
"manifest include had a conflicting `{}.{}`: {}",
outer_key,
key,
include_path.display()
)));
}
},
_ => {
// Cannot merge object and non-object
return Err(Error::validate(format!(
"manifest include had a conflicting `{}.{}`: {}",
outer_key,
key,
include_path.display()
)));
}
}
}
Ok(())
}
fn merge_facets(
&mut self,
other: &mut Document,
include_path: &path::Path,
) -> Result<(), Error> {
if let None = other.facets {
return Ok(());
}
if let None = self.facets {
self.facets = Some(Map::default());
}
let my_facets = self.facets.as_mut().unwrap();
let other_facets = other.facets.as_mut().unwrap();
Self::merge_maps(my_facets, other_facets, "facets", include_path)
}
pub fn includes(&self) -> Vec<String> {
self.include.clone().unwrap_or_default()
}
pub fn all_event_names(&self) -> Result<Vec<Name>, Error> {
let mut all_events: Vec<Name> = vec![];
if let Some(uses) = self.r#use.as_ref() {
for use_ in uses.iter() {
if let Some(event) = &use_.event {
let alias = use_.r#as();
let events: Vec<_> = event.to_vec();
if events.len() == 1 {
let event_name = alias_or_name(alias, &events[0]).clone();
all_events.push(event_name);
} else {
let mut events = events.into_iter().cloned().collect();
all_events.append(&mut events);
}
}
}
}
Ok(all_events)
}
pub fn all_children_names(&self) -> Vec<&Name> {
if let Some(children) = self.children.as_ref() {
children.iter().map(|c| &c.name).collect()
} else {
vec![]
}
}
pub fn all_collection_names(&self) -> Vec<&Name> {
if let Some(collections) = self.collections.as_ref() {
collections.iter().map(|c| &c.name).collect()
} else {
vec![]
}
}
pub fn all_storage_names(&self) -> Vec<&Name> {
if let Some(capabilities) = self.capabilities.as_ref() {
capabilities.iter().filter_map(|c| c.storage.as_ref()).collect()
} else {
vec![]
}
}
pub fn all_storage_and_sources<'a>(&'a self) -> HashMap<&'a Name, &'a CapabilityFromRef> {
if let Some(capabilities) = self.capabilities.as_ref() {
capabilities
.iter()
.filter_map(|c| match (c.storage.as_ref(), c.from.as_ref()) {
(Some(s), Some(f)) => Some((s, f)),
_ => None,
})
.collect()
} else {
HashMap::new()
}
}
pub fn all_service_names(&self) -> Vec<&Name> {
self.capabilities
.as_ref()
.map(|c| {
c.iter()
.filter_map(|c| c.service.as_ref())
.map(|p| p.to_vec().into_iter())
.flatten()
.collect()
})
.unwrap_or_else(|| vec![])
}
pub fn all_protocol_names(&self) -> Vec<&Name> {
self.capabilities
.as_ref()
.map(|c| {
c.iter()
.filter_map(|c| c.protocol.as_ref())
.map(|p| p.to_vec().into_iter())
.flatten()
.collect()
})
.unwrap_or_else(|| vec![])
}
pub fn all_directory_names(&self) -> Vec<&Name> {
self.capabilities
.as_ref()
.map(|c| c.iter().filter_map(|c| c.directory.as_ref()).collect())
.unwrap_or_else(|| vec![])
}
pub fn all_runner_names(&self) -> Vec<&Name> {
self.capabilities
.as_ref()
.map(|c| c.iter().filter_map(|c| c.runner.as_ref()).collect())
.unwrap_or_else(|| vec![])
}
pub fn all_resolver_names(&self) -> Vec<&Name> {
self.capabilities
.as_ref()
.map(|c| c.iter().filter_map(|c| c.resolver.as_ref()).collect())
.unwrap_or_else(|| vec![])
}
pub fn all_environment_names(&self) -> Vec<&Name> {
self.environments
.as_ref()
.map(|c| c.iter().map(|s| &s.name).collect())
.unwrap_or_else(|| vec![])
}
pub fn all_capability_names(&self) -> HashSet<Name> {
self.capabilities
.as_ref()
.map(|c| {
c.iter().fold(HashSet::new(), |mut acc, capability| {
acc.extend(capability.names());
acc
})
})
.unwrap_or_default()
}
}
#[derive(Deserialize, Debug, PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum EnvironmentExtends {
Realm,
None,
}
/// Example:
///
/// ```json5
/// environments: [
/// {
/// name: "test-env",
/// extend: "realm",
/// runners: [
/// {
/// runner: "gtest-runner",
/// from: "#gtest",
/// },
/// ],
/// resolvers: [
/// {
/// resolver: "universe-resolver",
/// from: "parent",
/// scheme: "fuchsia-pkg",
/// },
/// ],
/// },
/// ],
/// ```
#[derive(Deserialize, Debug, PartialEq, ReferenceDoc, Serialize)]
#[serde(deny_unknown_fields)]
#[reference_doc(fields_as = "list", top_level_doc_after_fields)]
pub struct Environment {
/// The name of the environment, which is a string of one or more of the
/// following characters: `a-z`, `0-9`, `_`, `.`, `-`. The name identifies this
/// environment when used in a [reference](#references).
pub name: Name,
/// How the environment should extend this realm's environment.
/// - `realm`: Inherit all properties from this compenent's environment.
/// - `none`: Start with an empty environment, do not inherit anything.
#[serde(skip_serializing_if = "Option::is_none")]
pub extends: Option<EnvironmentExtends>,
/// The runners registered in the environment. An array of objects
/// with the following properties:
#[reference_doc(recurse)]
#[serde(skip_serializing_if = "Option::is_none")]
pub runners: Option<Vec<RunnerRegistration>>,
/// The resolvers registered in the environment. An array of
/// objects with the following properties:
#[reference_doc(recurse)]
#[serde(skip_serializing_if = "Option::is_none")]
pub resolvers: Option<Vec<ResolverRegistration>>,
/// Debug protocols available to any component in this environment acquired
/// through `use from debug`.
#[reference_doc(recurse)]
#[serde(skip_serializing_if = "Option::is_none")]
pub debug: Option<Vec<DebugRegistration>>,
/// The number of milliseconds to wait, after notifying a component in this environment that it
/// should terminate, before forcibly killing it.
#[serde(rename(deserialize = "__stop_timeout_ms"))]
#[reference_doc(json_type = "number")]
#[serde(skip_serializing_if = "Option::is_none")]
pub stop_timeout_ms: Option<StopTimeoutMs>,
}
#[derive(Clone, Hash, Debug, PartialEq, PartialOrd, Eq, Ord, Serialize)]
pub struct ConfigKey(String);
impl ConfigKey {
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl ToString for ConfigKey {
fn to_string(&self) -> String {
self.0.clone()
}
}
impl FromStr for ConfigKey {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, ParseError> {
let length = s.len();
if length == 0 || length > 64 {
return Err(ParseError::InvalidLength);
}
// identifiers must start with a letter
let first_is_letter = s.chars().next().expect("non-empty string").is_ascii_lowercase();
// can contain letters, numbers, and underscores
let contains_invalid_chars =
s.chars().any(|c| !(c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_'));
// cannot end with an underscore
let last_is_underscore = s.chars().next_back().expect("non-empty string") == '_';
if !first_is_letter || contains_invalid_chars || last_is_underscore {
return Err(ParseError::InvalidValue);
}
Ok(Self(s.to_string()))
}
}
impl<'de> de::Deserialize<'de> for ConfigKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
struct Visitor;
impl<'de> de::Visitor<'de> for Visitor {
type Value = ConfigKey;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(
"a non-empty string no more than 64 characters in length, which must \
start with a letter, can contain letters, numbers, and underscores, \
but cannot end with an underscore",
)
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
s.parse().map_err(|err| match err {
ParseError::InvalidValue => E::invalid_value(
de::Unexpected::Str(s),
&"a name which must start with a letter, can contain letters, \
numbers, and underscores, but cannot end with an underscore",
),
ParseError::InvalidLength => E::invalid_length(
s.len(),
&"a non-empty name no more than 64 characters in length",
),
e => {
panic!("unexpected parse error: {:?}", e);
}
})
}
}
deserializer.deserialize_string(Visitor)
}
}
#[derive(Deserialize, Debug, PartialEq, Serialize)]
#[serde(tag = "type", deny_unknown_fields, rename_all = "lowercase")]
pub enum ConfigValueType {
Bool,
Uint8,
Uint16,
Uint32,
Uint64,
Int8,
Int16,
Int32,
Int64,
String { max_size: NonZeroU32 },
Vector { max_count: NonZeroU32, element: ConfigNestedValueType },
}
impl ConfigValueType {
/// Update the hasher by digesting the ConfigValueType enum value
pub fn update_digest(&self, hasher: &mut impl sha2::Digest) {
let val = match self {
ConfigValueType::Bool => 0u8,
ConfigValueType::Uint8 => 1u8,
ConfigValueType::Uint16 => 2u8,
ConfigValueType::Uint32 => 3u8,
ConfigValueType::Uint64 => 4u8,
ConfigValueType::Int8 => 5u8,
ConfigValueType::Int16 => 6u8,
ConfigValueType::Int32 => 7u8,
ConfigValueType::Int64 => 8u8,
ConfigValueType::String { max_size } => {
hasher.update(max_size.get().to_le_bytes());
9u8
}
ConfigValueType::Vector { max_count, element } => {
hasher.update(max_count.get().to_le_bytes());
element.update_digest(hasher);
10u8
}
};
hasher.update([val])
}
}
#[derive(Deserialize, Debug, PartialEq, Serialize)]
#[serde(tag = "type", deny_unknown_fields, rename_all = "lowercase")]
pub enum ConfigNestedValueType {
Bool,
Uint8,
Uint16,
Uint32,
Uint64,
Int8,
Int16,
Int32,
Int64,
String { max_size: NonZeroU32 },
}
impl ConfigNestedValueType {
/// Update the hasher by digesting the ConfigVectorElementType enum value
pub fn update_digest(&self, hasher: &mut impl sha2::Digest) {
let val = match self {
ConfigNestedValueType::Bool => 0u8,
ConfigNestedValueType::Uint8 => 1u8,
ConfigNestedValueType::Uint16 => 2u8,
ConfigNestedValueType::Uint32 => 3u8,
ConfigNestedValueType::Uint64 => 4u8,
ConfigNestedValueType::Int8 => 5u8,
ConfigNestedValueType::Int16 => 6u8,
ConfigNestedValueType::Int32 => 7u8,
ConfigNestedValueType::Int64 => 8u8,
ConfigNestedValueType::String { max_size } => {
hasher.update(max_size.get().to_le_bytes());
9u8
}
};
hasher.update([val])
}
}
#[derive(Deserialize, Debug, PartialEq, ReferenceDoc, Serialize)]
#[serde(deny_unknown_fields)]
#[reference_doc(fields_as = "list")]
pub struct RunnerRegistration {
/// The [name](#name) of a runner capability, whose source is specified in `from`.
pub runner: Name,
/// The source of the runner capability, one of:
/// - `parent`: The component's parent.
/// - `self`: This component.
/// - `#<child-name>`: A [reference](#references) to a child component
/// instance.
pub from: RegistrationRef,
/// An explicit name for the runner as it will be known in
/// this environment. If omitted, defaults to `runner`.
pub r#as: Option<Name>,
}
#[derive(Deserialize, Debug, PartialEq, ReferenceDoc, Serialize)]
#[serde(deny_unknown_fields)]
#[reference_doc(fields_as = "list")]
pub struct ResolverRegistration {
/// The [name](#name) of a resolver capability,
/// whose source is specified in `from`.
pub resolver: Name,
/// The source of the resolver capability, one of:
/// - `parent`: The component's parent.
/// - `self`: This component.
/// - `#<child-name>`: A [reference](#references) to a child component
/// instance.
pub from: RegistrationRef,
/// The URL scheme for which the resolver should handle
/// resolution.
pub scheme: cm_types::UrlScheme,
}
#[derive(Deserialize, Debug, PartialEq, Clone, ReferenceDoc, Serialize)]
#[serde(deny_unknown_fields)]
#[reference_doc(fields_as = "list")]
pub struct Capability {
/// The [name](#name) for this service capability. Specifying `path` is valid
/// only when this value is a string.
#[serde(skip_serializing_if = "Option::is_none")]
pub service: Option<OneOrMany<Name>>,
/// The [name](#name) for this protocol capability. Specifying `path` is valid
/// only when this value is a string.
#[serde(skip_serializing_if = "Option::is_none")]
pub protocol: Option<OneOrMany<Name>>,
/// The [name](#name) for this directory capability.
#[serde(skip_serializing_if = "Option::is_none")]
pub directory: Option<Name>,
/// The [name](#name) for this storage capability.
#[serde(skip_serializing_if = "Option::is_none")]
pub storage: Option<Name>,
/// The [name](#name) for this runner capability.
#[serde(skip_serializing_if = "Option::is_none")]
pub runner: Option<Name>,
/// The [name](#name) for this resolver capability.
#[serde(skip_serializing_if = "Option::is_none")]
pub resolver: Option<Name>,
/// The [name](#name) for this event capability.
#[serde(skip_serializing_if = "Option::is_none")]
pub event: Option<Name>,
/// The [name](#name) for this event_stream capability.
#[serde(skip_serializing_if = "Option::is_none")]
pub event_stream: Option<OneOrMany<Name>>,
/// The path within the [outgoing directory][glossary.outgoing directory] of the component's
/// program to source the capability.
///
/// For `protocol` and `service`, defaults to `/svc/${protocol}`, otherwise required.
///
/// For `protocol`, the target of the path MUST be a channel, which tends to speak
/// the protocol matching the name of this capability.
///
/// For `service`, `directory`, the target of the path MUST be a directory.
///
/// For `runner`, the target of the path MUST be a channel and MUST speak
/// the protocol `fuchsia.component.runner.ComponentRunner`.
///
/// For `resolver`, the target of the path MUST be a channel and MUST speak
/// the protocol `fuchsia.component.resolution.Resolver`.
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<Path>,
/// (`directory` only) The maximum [directory rights][doc-directory-rights] that may be set
/// when using this directory.
#[serde(skip_serializing_if = "Option::is_none")]
pub rights: Option<Rights>,
/// (`storage` only) The source component of an existing directory capability backing this
/// storage capability, one of:
/// - `parent`: The component's parent.
/// - `self`: This component.
/// - `#<child-name>`: A [reference](#references) to a child component
/// instance.
#[serde(skip_serializing_if = "Option::is_none")]
pub from: Option<CapabilityFromRef>,
/// (`storage` only) The [name](#name) of the directory capability backing the storage. The
/// capability must be available from the component referenced in `from`.
#[serde(skip_serializing_if = "Option::is_none")]
pub backing_dir: Option<Name>,
/// (`storage` only) A subdirectory within `backing_dir` where per-component isolated storage
/// directories are created
#[serde(skip_serializing_if = "Option::is_none")]
pub subdir: Option<RelativePath>,
/// (`storage only`) The identifier used to isolated storage for a component, one of:
/// - `static_instance_id`: The instance ID in the component ID index is used
/// as the key for a component's storage. Components which are not listed in
/// the component ID index will not be able to use this storage capability.
/// - `static_instance_id_or_moniker`: If the component is listed in the
/// component ID index, the instance ID is used as the key for a component's
/// storage. Otherwise, the component's relative moniker from the storage
/// capability is used.
#[serde(skip_serializing_if = "Option::is_none")]
pub storage_id: Option<StorageId>,
}
#[derive(Deserialize, Debug, PartialEq, ReferenceDoc, Serialize)]
#[serde(deny_unknown_fields)]
#[reference_doc(fields_as = "list")]
pub struct DebugRegistration {
/// The name(s) of the protocol(s) to make available.
pub protocol: Option<OneOrMany<Name>>,
/// The source of the capability(s), one of:
/// - `parent`: The component's parent.
/// - `self`: This component.
/// - `#<child-name>`: A [reference](#references) to a child component
/// instance.
pub from: OfferFromRef,
/// If specified, the name that the capability in `protocol` should be made
/// available as to clients. Disallowed if `protocol` is an array.
pub r#as: Option<Name>,
}
/// A list of event modes.
#[derive(CheckedVec, Debug, PartialEq, Clone)]
#[checked_vec(
expected = "a nonempty array of event subscriptions",
min_length = 1,
unique_items = false
)]
pub struct EventSubscriptions(pub Vec<EventSubscription>);
impl Deref for EventSubscriptions {
type Target = Vec<EventSubscription>;
fn deref(&self) -> &Vec<EventSubscription> {
&self.0
}
}
#[derive(Debug, PartialEq, Default, Serialize)]
pub struct Program {
#[serde(skip_serializing_if = "Option::is_none")]
pub runner: Option<Name>,
#[serde(flatten)]
pub info: Map<String, Value>,
}
impl<'de> de::Deserialize<'de> for Program {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
struct Visitor;
const EXPECTED_PROGRAM: &'static str =
"a JSON object that includes a `runner` string property";
const EXPECTED_RUNNER: &'static str =
"a non-empty `runner` string property no more than 100 characters in length \
that consists of [A-Za-z0-9_.-] and starts with [A-Za-z0-9_]";
impl<'de> de::Visitor<'de> for Visitor {
type Value = Program;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(EXPECTED_PROGRAM)
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: de::MapAccess<'de>,
{
let mut info = Map::new();
let mut runner = None;
while let Some(e) = map.next_entry::<String, Value>()? {
let (k, v) = e;
if &k == "runner" {
if let Value::String(s) = v {
runner = Some(s);
} else {
return Err(de::Error::invalid_value(
de::Unexpected::Map,
&EXPECTED_RUNNER,
));
}
} else {
info.insert(k, v);
}
}
let runner = runner
.map(|r| {
Name::new(r.clone()).map_err(|e| match e {
ParseError::InvalidValue => de::Error::invalid_value(
serde::de::Unexpected::Str(&r),
&EXPECTED_RUNNER,
),
ParseError::InvalidLength => {
de::Error::invalid_length(r.len(), &EXPECTED_RUNNER)
}
_ => {
panic!("unexpected parse error: {:?}", e);
}
})
})
.transpose()?;
Ok(Program { runner, info })
}
}
deserializer.deserialize_map(Visitor)
}
}
/// Example:
///
/// ```json5
/// use: [
/// {
/// protocol: [
/// "fuchsia.ui.scenic.Scenic",
/// "fuchsia.accessibility.Manager",
/// ]
/// },
/// {
/// directory: "themes",
/// path: "/data/themes",
/// rights: [ "r*" ],
/// },
/// {
/// storage: "persistent",
/// path: "/data",
/// },
/// {
/// event: [
/// "started",
/// "stopped",
/// ],
/// from: "framework",
/// },
/// ],
/// ```
#[derive(Deserialize, Debug, Default, PartialEq, Clone, ReferenceDoc, Serialize)]
#[serde(deny_unknown_fields)]
#[reference_doc(fields_as = "list", top_level_doc_after_fields)]
pub struct Use {
/// When using a service capability, the [name](#name) of a [service capability][doc-service].
#[serde(skip_serializing_if = "Option::is_none")]
pub service: Option<OneOrMany<Name>>,
/// When using a protocol capability, the [name](#name) of a [protocol capability][doc-protocol].
#[serde(skip_serializing_if = "Option::is_none")]
pub protocol: Option<OneOrMany<Name>>,
/// When using a directory capability, the [name](#name) of a [directory capability][doc-directory].
#[serde(skip_serializing_if = "Option::is_none")]
pub directory: Option<Name>,
/// When using a storage capability, the [name](#name) of a [storage capability][doc-storage].
#[serde(skip_serializing_if = "Option::is_none")]
pub storage: Option<Name>,
/// When using an event capability, the [name](#name) of an [event capability][doc-event].
#[serde(skip_serializing_if = "Option::is_none")]
pub event: Option<OneOrMany<Name>>,
/// Deprecated.
#[serde(skip_serializing_if = "Option::is_none")]
pub event_stream_deprecated: Option<Name>,
/// When using an event stream capability, the [name](#name) of an [event stream capability][doc-event].
#[serde(skip_serializing_if = "Option::is_none")]
pub event_stream: Option<OneOrMany<Name>>,
/// The source of the capability. Defaults to `parent`. One of:
/// - `parent`: The component's parent.
/// - `debug`: One of [`debug_capabilities`][fidl-environment-decl] in the
/// environment assigned to this component.
/// - `framework`: The Component Framework runtime.
/// - `self`: This component.
/// - `#<capability-name>`: The name of another capability from which the
/// requested capability is derived.
/// - `#<child-name>`: A [reference](#references) to a child component
/// instance.
#[serde(skip_serializing_if = "Option::is_none")]
pub from: Option<UseFromRef>,
/// The path at which to install the capability in the component's namespace. For protocols,
/// defaults to `/svc/${protocol}`. Required for `directory` and `storage`. This property is
/// disallowed for declarations with arrays of capability names.
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<Path>,
/// (`directory` only) the maximum [directory rights][doc-directory-rights] to apply to
/// the directory in the component's namespace.
#[serde(skip_serializing_if = "Option::is_none")]
pub rights: Option<Rights>,
/// (`directory` only) A subdirectory within the directory capability to provide in the
/// component's namespace.
#[serde(skip_serializing_if = "Option::is_none")]
pub subdir: Option<RelativePath>,
/// TODO(fxb/96705): Document events features.
#[serde(skip_serializing_if = "Option::is_none")]
pub r#as: Option<Name>,
/// TODO(fxb/96705): Document events features.
#[serde(skip_serializing_if = "Option::is_none")]
pub scope: Option<OneOrMany<EventScope>>,
/// TODO(fxb/96705): Document events features.
#[serde(skip_serializing_if = "Option::is_none")]
pub filter: Option<Map<String, Value>>,
/// TODO(fxb/96705): Document events features.
#[serde(skip_serializing_if = "Option::is_none")]
pub subscriptions: Option<EventSubscriptions>,
/// `dependency` _(optional)_: The type of dependency between the source and
/// this component, one of:
/// - `strong`: a strong dependency, which is used to determine shutdown
/// ordering. Component manager is guaranteed to stop the target before the
/// source. This is the default.
/// - `weak_for_migration`: a weak dependency, which is ignored during
/// shutdown. When component manager stops the parent realm, the source may
/// stop before the clients. Clients of weak dependencies must be able to
/// handle these dependencies becoming unavailable. This type exists to keep
/// track of weak dependencies that resulted from migrations into v2
/// components.
#[serde(skip_serializing_if = "Option::is_none")]
pub dependency: Option<DependencyType>,
/// Determines whether this capability is required to be available, or if its presence is
/// optional.
#[serde(skip_serializing_if = "Option::is_none")]
pub availability: Option<Availability>,
}
/// Example:
///
/// ```json5
/// expose: [
/// {
/// directory: "themes",
/// from: "self",
/// },
/// {
/// protocol: "pkg.Cache",
/// from: "#pkg_cache",
/// as: "fuchsia.pkg.PackageCache",
/// },
/// {
/// protocol: [
/// "fuchsia.ui.app.ViewProvider",
/// "fuchsia.fonts.Provider",
/// ],
/// from: "self",
/// },
/// {
/// runner: "web-chromium",
/// from: "#web_runner",
/// as: "web",
/// },
/// {
/// resolver: "universe-resolver",
/// from: "#universe_resolver",
/// },
/// ],
/// ```
#[derive(Deserialize, Debug, PartialEq, Clone, ReferenceDoc, Serialize)]
#[serde(deny_unknown_fields)]
#[reference_doc(fields_as = "list", top_level_doc_after_fields)]
pub struct Expose {
/// When routing a service, the [name](#name) of a [service capability][doc-service].
#[serde(skip_serializing_if = "Option::is_none")]
pub service: Option<OneOrMany<Name>>,
/// When routing a protocol, the [name](#name) of a [protocol capability][doc-protocol].
#[serde(skip_serializing_if = "Option::is_none")]
pub protocol: Option<OneOrMany<Name>>,
/// When routing a directory, the [name](#name) of a [directory capability][doc-directory].
#[serde(skip_serializing_if = "Option::is_none")]
pub directory: Option<OneOrMany<Name>>,
/// When routing a runner, the [name](#name) of a [runner capability][doc-runners].
#[serde(skip_serializing_if = "Option::is_none")]
pub runner: Option<OneOrMany<Name>>,
/// When routing a resolver, the [name](#name) of a [resolver capability][doc-resolvers].
#[serde(skip_serializing_if = "Option::is_none")]
pub resolver: Option<OneOrMany<Name>>,
/// `from`: The source of the capability, one of:
/// - `self`: This component. Requires a corresponding
/// [`capability`](#capabilities) declaration.
/// - `framework`: The Component Framework runtime.
/// - `#<child-name>`: A [reference](#references) to a child component
/// instance.
pub from: OneOrMany<ExposeFromRef>,
/// The [name](#name) for the capability as it will be known by the target. If omitted,
/// defaults to the original name. `as` cannot be used when an array of multiple capability
/// names is provided.
#[serde(skip_serializing_if = "Option::is_none")]
pub r#as: Option<Name>,
/// The capability target. Either `parent` or `framework`. Defaults to `parent`.
#[serde(skip_serializing_if = "Option::is_none")]
pub to: Option<ExposeToRef>,
/// (`directory` only) the maximum [directory rights][doc-directory-rights] to apply to
/// the exposed directory capability.
#[serde(skip_serializing_if = "Option::is_none")]
pub rights: Option<Rights>,
/// (`directory` only) the relative path of a subdirectory within the source directory
/// capability to route.
#[serde(skip_serializing_if = "Option::is_none")]
pub subdir: Option<RelativePath>,
/// TODO(fxb/96705): Complete.
#[serde(skip_serializing_if = "Option::is_none")]
pub event_stream: Option<OneOrMany<Name>>,
/// TODO(fxb/96705): Complete.
#[serde(skip_serializing_if = "Option::is_none")]
pub scope: Option<OneOrMany<EventScope>>,
}
/// Example:
///
/// ```json5
/// offer: [
/// {
/// protocol: "fuchsia.logger.LogSink",
/// from: "#logger",
/// to: [ "#fshost", "#pkg_cache" ],
/// dependency: "weak_for_migration",
/// },
/// {
/// protocol: [
/// "fuchsia.ui.app.ViewProvider",
/// "fuchsia.fonts.Provider",
/// ],
/// from: "#session",
/// to: [ "#ui_shell" ],
/// dependency: "strong",
/// },
/// {
/// directory: "blobfs",
/// from: "self",
/// to: [ "#pkg_cache" ],
/// },
/// {
/// directory: "fshost-config",
/// from: "parent",
/// to: [ "#fshost" ],
/// as: "config",
/// },
/// {
/// storage: "cache",
/// from: "parent",
/// to: [ "#logger" ],
/// },
/// {
/// runner: "web",
/// from: "parent",
/// to: [ "#user-shell" ],
/// },
/// {
/// resolver: "universe-resolver",
/// from: "parent",
/// to: [ "#user-shell" ],
/// },
/// {
/// event: "stopped",
/// from: "framework",
/// to: [ "#logger" ],
/// },
/// ],
/// ```
#[derive(Deserialize, Debug, PartialEq, Clone, ReferenceDoc, Serialize)]
#[serde(deny_unknown_fields)]
#[reference_doc(fields_as = "list", top_level_doc_after_fields)]
pub struct Offer {
/// When routing a service, the [name](#name) of a [service capability][doc-service].
#[serde(skip_serializing_if = "Option::is_none")]
pub service: Option<OneOrMany<Name>>,
/// When routing a protocol, the [name](#name) of a [protocol capability][doc-protocol].
#[serde(skip_serializing_if = "Option::is_none")]
pub protocol: Option<OneOrMany<Name>>,
/// When routing a directory, the [name](#name) of a [directory capability][doc-directory].
#[serde(skip_serializing_if = "Option::is_none")]
pub directory: Option<OneOrMany<Name>>,
/// When routing a runner, the [name](#name) of a [runner capability][doc-runners].
#[serde(skip_serializing_if = "Option::is_none")]
pub runner: Option<OneOrMany<Name>>,
/// When routing a resolver, the [name](#name) of a [resolver capability][doc-resolvers].
#[serde(skip_serializing_if = "Option::is_none")]
pub resolver: Option<OneOrMany<Name>>,
/// When routing a storage capability, the [name](#name) of a [storage capability][doc-storage].
#[serde(skip_serializing_if = "Option::is_none")]
pub storage: Option<OneOrMany<Name>>,
/// When routing an event, the [name](#name) of the [event][doc-event].
#[serde(skip_serializing_if = "Option::is_none")]
pub event: Option<OneOrMany<Name>>,
/// `from`: The source of the capability, one of:
/// - `parent`: The component's parent. This source can be used for all
/// capability types.
/// - `self`: This component. Requires a corresponding
/// [`capability`](#capabilities) declaration.
/// - `framework`: The Component Framework runtime.
/// - `#<child-name>`: A [reference](#references) to a child component
/// instance. This source can only be used when offering protocol,
/// directory, or runner capabilities.
/// - `void`: The source is intentionally omitted. Only valid when `availability` is not
/// `required`.
pub from: OneOrMany<OfferFromRef>,
/// A capability target or array of targets, each of which is a [reference](#references) to the
/// child or collection to which the capability is being offered, of the form `#<target-name>`.
pub to: OneOrMany<OfferToRef>,
/// An explicit [name](#name) for the capability as it will be known by the target. If omitted,
/// defaults to the original name. `as` cannot be used when an array of multiple names is
/// provided.
#[serde(skip_serializing_if = "Option::is_none")]
pub r#as: Option<Name>,
/// The type of dependency between the source and
/// targets, one of:
/// - `strong`: a strong dependency, which is used to determine shutdown
/// ordering. Component manager is guaranteed to stop the target before the
/// source. This is the default.
/// - `weak_for_migration`: a weak dependency, which is ignored during
/// shutdown. When component manager stops the parent realm, the source may
/// stop before the clients. Clients of weak dependencies must be able to
/// handle these dependencies becoming unavailable. This type exists to keep
/// track of weak dependencies that resulted from migrations into v2
/// components.
#[serde(skip_serializing_if = "Option::is_none")]
pub dependency: Option<DependencyType>,
/// (`directory` only) the maximum [directory rights][doc-directory-rights] to apply to
/// the offered directory capability.
#[serde(skip_serializing_if = "Option::is_none")]
pub rights: Option<Rights>,
/// (`directory` only) the relative path of a subdirectory within the source directory
/// capability to route.
#[serde(skip_serializing_if = "Option::is_none")]
pub subdir: Option<RelativePath>,
/// TODO(fxb/96705): Complete.
#[serde(skip_serializing_if = "Option::is_none")]
pub filter: Option<Map<String, Value>>,
/// TODO(fxb/96705): Complete.
#[serde(skip_serializing_if = "Option::is_none")]
pub event_stream: Option<OneOrMany<Name>>,
/// TODO(fxb/96705): Complete.
#[serde(skip_serializing_if = "Option::is_none")]
pub scope: Option<OneOrMany<EventScope>>,
/// Whether or not this capability must be present. Defaults to `required`.
#[serde(skip_serializing_if = "Option::is_none")]
pub availability: Option<Availability>,
/// Whether or not the source of this offer must exist. If set to `unknown`, the source of this
/// offer will be rewritten to `void` if the source does not exist (i.e. is not defined in this
/// manifest). The availability must be set to `optional` when `source_availability` is set to
/// `unknown`. Defaults to `required`.
#[serde(skip_serializing_if = "Option::is_none")]
pub source_availability: Option<SourceAvailability>,
}
/// Example:
///
/// ```json5
/// children: [
/// {
/// name: "logger",
/// url: "fuchsia-pkg://fuchsia.com/logger#logger.cm",
/// },
/// {
/// name: "pkg_cache",
/// url: "fuchsia-pkg://fuchsia.com/pkg_cache#meta/pkg_cache.cm",
/// startup: "eager",
/// },
/// {
/// name: "child",
/// url: "#meta/child.cm",
/// }
/// ],
/// ```
///
/// [component-url]: /docs/concepts/components/component_urls.md
/// [doc-eager]: /docs/development/components/connect.md#eager
/// [doc-reboot-on-terminate]: /docs/development/components/connect.md#reboot-on-terminate
#[derive(ReferenceDoc, Deserialize, Debug, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
#[reference_doc(fields_as = "list", top_level_doc_after_fields)]
pub struct Child {
/// The name of the child component instance, which is a string of one
/// or more of the following characters: `a-z`, `0-9`, `_`, `.`, `-`. The name
/// identifies this component when used in a [reference](#references).
pub name: Name,
/// The [component URL][component-url] for the child component instance.
pub url: Url,
/// The component instance's startup mode. One of:
/// - `lazy` _(default)_: Start the component instance only if another
/// component instance binds to it.
/// - [`eager`][doc-eager]: Start the component instance as soon as its parent
/// starts.
#[serde(default)]
#[serde(skip_serializing_if = "StartupMode::is_lazy")]
pub startup: StartupMode,
/// Determines the fault recovery policy to apply if this component terminates.
/// - `none` _(default)_: Do nothing.
/// - `reboot`: Gracefully reboot the system if the component terminates for
/// any reason. This is a special feature for use only by a narrow set of
/// components; see [Termination policies][doc-reboot-on-terminate] for more
/// information.
#[serde(skip_serializing_if = "Option::is_none")]
pub on_terminate: Option<OnTerminate>,
/// If present, the name of the environment to be assigned to the child component instance, one
/// of [`environments`](#environments). If omitted, the child will inherit the same environment
/// assigned to this component.
#[serde(skip_serializing_if = "Option::is_none")]
pub environment: Option<EnvironmentRef>,
}
#[derive(Deserialize, Debug, PartialEq, ReferenceDoc, Serialize)]
#[serde(deny_unknown_fields)]
#[reference_doc(fields_as = "list", top_level_doc_after_fields)]
/// Example:
///
/// ```json5
/// collections: [
/// {
/// name: "tests",
/// durability: "transient",
/// },
/// ],
/// ```
pub struct Collection {
/// The name of the component collection, which is a string of one or
/// more of the following characters: `a-z`, `0-9`, `_`, `.`, `-`. The name
/// identifies this collection when used in a [reference](#references).
pub name: Name,
/// The duration of child component instances in the collection.
/// - `transient`: The instance exists until its parent is stopped or it is
/// explicitly destroyed.
/// - `single_run`: The instance is started when it is created, and destroyed
/// when it is stopped.
pub durability: Durability,
/// If present, the environment that will be
/// assigned to instances in this collection, one of
/// [`environments`](#environments). If omitted, instances in this collection
/// will inherit the same environment assigned to this component.
pub environment: Option<EnvironmentRef>,
/// Constraints on the dynamic offers that target the components in this collection.
/// Dynamic offers are specified when calling `fuchsia.component.Realm/CreateChild`.
/// - `static_only`: Only those specified in this `.cml` file. No dynamic offers.
/// This is the default.
/// - `static_and_dynamic`: Both static offers and those specified at runtime
/// with `CreateChild` are allowed.
pub allowed_offers: Option<AllowedOffers>,
/// Allow child names up to 1024 characters long instead of the usual 100 character limit.
/// Default is false.
pub allow_long_names: Option<bool>,
/// If set to `true`, the data in isolated storage used by dynamic child instances and
/// their descendants will persist after the instances are destroyed. A new child instance
/// created with the same name will share the same storage path as the previous instance.
pub persistent_storage: Option<bool>,
}
pub trait FromClause {
fn from_(&self) -> OneOrMany<AnyRef<'_>>;
}
pub trait CapabilityClause {
fn service(&self) -> Option<OneOrMany<Name>>;
fn protocol(&self) -> Option<OneOrMany<Name>>;
fn directory(&self) -> Option<OneOrMany<Name>>;
fn storage(&self) -> Option<OneOrMany<Name>>;
fn runner(&self) -> Option<OneOrMany<Name>>;
fn resolver(&self) -> Option<OneOrMany<Name>>;
fn event(&self) -> Option<OneOrMany<Name>>;
fn event_stream_deprecated(&self) -> Option<Name>;
fn event_stream(&self) -> Option<OneOrMany<Name>>;
/// Returns the name of the capability for display purposes.
/// If `service()` returns `Some`, the capability name must be "service", etc.
///
/// Panics if a capability keyword is not set.
fn capability_type(&self) -> &'static str;
fn decl_type(&self) -> &'static str;
fn supported(&self) -> &[&'static str];
/// Returns the names of the capabilities in this clause.
/// If `protocol()` returns `Some(OneOrMany::Many(vec!["a", "b"]))`, this returns!["a", "b"].
fn names(&self) -> Vec<Name> {
let res = vec![
self.service(),
self.protocol(),
self.directory(),
self.storage(),
self.runner(),
self.resolver(),
self.event(),
self.event_stream_deprecated().map(|n| OneOrMany::One(n)),
];
res.into_iter()
.map(|o| o.map(|o| o.into_iter().collect::<Vec<Name>>()).unwrap_or(vec![]))
.flatten()
.collect()
}
}
pub trait AsClause {
fn r#as(&self) -> Option<&Name>;
}
pub trait PathClause {
fn path(&self) -> Option<&Path>;
}
pub trait FilterClause {
fn filter(&self) -> Option<&Map<String, Value>>;
}
pub trait EventSubscriptionsClause {
fn event_subscriptions(&self) -> Option<&EventSubscriptions>;
}
pub trait RightsClause {
fn rights(&self) -> Option<&Rights>;
}
impl CapabilityClause for Capability {
fn service(&self) -> Option<OneOrMany<Name>> {
self.service.clone()
}
fn protocol(&self) -> Option<OneOrMany<Name>> {
self.protocol.clone()
}
fn directory(&self) -> Option<OneOrMany<Name>> {
self.directory.as_ref().map(|n| OneOrMany::One(n.clone()))
}
fn storage(&self) -> Option<OneOrMany<Name>> {
self.storage.as_ref().map(|n| OneOrMany::One(n.clone()))
}
fn runner(&self) -> Option<OneOrMany<Name>> {
self.runner.as_ref().map(|n| OneOrMany::One(n.clone()))
}
fn resolver(&self) -> Option<OneOrMany<Name>> {
self.resolver.as_ref().map(|n| OneOrMany::One(n.clone()))
}
fn event(&self) -> Option<OneOrMany<Name>> {
self.event.as_ref().map(|n| OneOrMany::One(n.clone()))
}
fn event_stream(&self) -> Option<OneOrMany<Name>> {
self.event_stream.clone()
}
fn event_stream_deprecated(&self) -> Option<Name> {
None
}
fn capability_type(&self) -> &'static str {
if self.service.is_some() {
"service"
} else if self.protocol.is_some() {
"protocol"
} else if self.directory.is_some() {
"directory"
} else if self.storage.is_some() {
"storage"
} else if self.runner.is_some() {
"runner"
} else if self.resolver.is_some() {
"resolver"
} else if self.event.is_some() {
"event"
} else if self.event_stream.is_some() {
"event_stream"
} else {
panic!("Missing capability name")
}
}
fn decl_type(&self) -> &'static str {
"capability"
}
fn supported(&self) -> &[&'static str] {
&[
"service",
"protocol",
"directory",
"storage",
"runner",
"resolver",
"event",
"event_stream",
]
}
}
impl AsClause for Capability {
fn r#as(&self) -> Option<&Name> {
None
}
}
impl PathClause for Capability {
fn path(&self) -> Option<&Path> {
self.path.as_ref()
}
}
impl FilterClause for Capability {
fn filter(&self) -> Option<&Map<String, Value>> {
None
}
}
impl RightsClause for Capability {
fn rights(&self) -> Option<&Rights> {
self.rights.as_ref()
}
}
impl CapabilityClause for DebugRegistration {
fn service(&self) -> Option<OneOrMany<Name>> {
None
}
fn protocol(&self) -> Option<OneOrMany<Name>> {
self.protocol.clone()
}
fn directory(&self) -> Option<OneOrMany<Name>> {
None
}
fn storage(&self) -> Option<OneOrMany<Name>> {
None
}
fn runner(&self) -> Option<OneOrMany<Name>> {
None
}
fn resolver(&self) -> Option<OneOrMany<Name>> {
None
}
fn event(&self) -> Option<OneOrMany<Name>> {
None
}
fn event_stream(&self) -> Option<OneOrMany<Name>> {
None
}
fn event_stream_deprecated(&self) -> Option<Name> {
None
}
fn capability_type(&self) -> &'static str {
if self.protocol.is_some() {
"protocol"
} else {
panic!("Missing capability name")
}
}
fn decl_type(&self) -> &'static str {
"debug"
}
fn supported(&self) -> &[&'static str] {
&["service", "protocol"]
}
}
impl AsClause for DebugRegistration {
fn r#as(&self) -> Option<&Name> {
self.r#as.as_ref()
}
}
impl PathClause for DebugRegistration {
fn path(&self) -> Option<&Path> {
None
}
}
impl FromClause for DebugRegistration {
fn from_(&self) -> OneOrMany<AnyRef<'_>> {
OneOrMany::One(AnyRef::from(&self.from))
}
}
impl CapabilityClause for Use {
fn service(&self) -> Option<OneOrMany<Name>> {
self.service.clone()
}
fn protocol(&self) -> Option<OneOrMany<Name>> {
self.protocol.clone()
}
fn directory(&self) -> Option<OneOrMany<Name>> {
self.directory.as_ref().map(|n| OneOrMany::One(n.clone()))
}
fn storage(&self) -> Option<OneOrMany<Name>> {
self.storage.as_ref().map(|n| OneOrMany::One(n.clone()))
}
fn runner(&self) -> Option<OneOrMany<Name>> {
None
}
fn resolver(&self) -> Option<OneOrMany<Name>> {
None
}
fn event(&self) -> Option<OneOrMany<Name>> {
self.event.clone()
}
fn event_stream_deprecated(&self) -> Option<Name> {
self.event_stream_deprecated.clone()
}
fn event_stream(&self) -> Option<OneOrMany<Name>> {
self.event_stream.clone()
}
fn capability_type(&self) -> &'static str {
if self.service.is_some() {
"service"
} else if self.protocol.is_some() {
"protocol"
} else if self.directory.is_some() {
"directory"
} else if self.storage.is_some() {
"storage"
} else if self.event.is_some() {
"event"
} else if self.event_stream_deprecated.is_some() {
"event_stream_deprecated"
} else if self.event_stream.is_some() {
"event_stream"
} else {
panic!("Missing capability name")
}
}
fn decl_type(&self) -> &'static str {
"use"
}
fn supported(&self) -> &[&'static str] {
&[
"service",
"protocol",
"directory",
"storage",
"runner",
"event",
"event_stream",
"event_stream_deprecated",
]
}
}
impl FilterClause for Use {
fn filter(&self) -> Option<&Map<String, Value>> {
self.filter.as_ref()
}
}
impl AsClause for Use {
fn r#as(&self) -> Option<&Name> {
self.r#as.as_ref()
}
}
impl PathClause for Use {
fn path(&self) -> Option<&Path> {
self.path.as_ref()
}
}
impl FromClause for Expose {
fn from_(&self) -> OneOrMany<AnyRef<'_>> {
one_or_many_from_impl(&self.from)
}
}
impl RightsClause for Use {
fn rights(&self) -> Option<&Rights> {
self.rights.as_ref()
}
}
impl EventSubscriptionsClause for Use {
fn event_subscriptions(&self) -> Option<&EventSubscriptions> {
self.subscriptions.as_ref()
}
}
impl CapabilityClause for Expose {
fn service(&self) -> Option<OneOrMany<Name>> {
self.service.as_ref().map(|n| n.clone())
}
fn protocol(&self) -> Option<OneOrMany<Name>> {
self.protocol.clone()
}
fn directory(&self) -> Option<OneOrMany<Name>> {
self.directory.clone()
}
fn storage(&self) -> Option<OneOrMany<Name>> {
None
}
fn runner(&self) -> Option<OneOrMany<Name>> {
self.runner.clone()
}
fn resolver(&self) -> Option<OneOrMany<Name>> {
self.resolver.clone()
}
fn event(&self) -> Option<OneOrMany<Name>> {
None
}
fn event_stream_deprecated(&self) -> Option<Name> {
None
}
fn event_stream(&self) -> Option<OneOrMany<Name>> {
self.event_stream.clone()
}
fn capability_type(&self) -> &'static str {
if self.service.is_some() {
"service"
} else if self.protocol.is_some() {
"protocol"
} else if self.directory.is_some() {
"directory"
} else if self.runner.is_some() {
"runner"
} else if self.resolver.is_some() {
"resolver"
} else if self.event_stream.is_some() {
"event_stream"
} else {
panic!("Missing capability name")
}
}
fn decl_type(&self) -> &'static str {
"expose"
}
fn supported(&self) -> &[&'static str] {
&["service", "protocol", "directory", "runner", "resolver", "event_stream"]
}
}
impl AsClause for Expose {
fn r#as(&self) -> Option<&Name> {
self.r#as.as_ref()
}
}
impl PathClause for Expose {
fn path(&self) -> Option<&Path> {
None
}
}
impl FilterClause for Expose {
fn filter(&self) -> Option<&Map<String, Value>> {
None
}
}
impl RightsClause for Expose {
fn rights(&self) -> Option<&Rights> {
self.rights.as_ref()
}
}
impl FromClause for Offer {
fn from_(&self) -> OneOrMany<AnyRef<'_>> {
one_or_many_from_impl(&self.from)
}
}
impl CapabilityClause for Offer {
fn service(&self) -> Option<OneOrMany<Name>> {
self.service.as_ref().map(|n| n.clone())
}
fn protocol(&self) -> Option<OneOrMany<Name>> {
self.protocol.clone()
}
fn directory(&self) -> Option<OneOrMany<Name>> {
self.directory.clone()
}
fn storage(&self) -> Option<OneOrMany<Name>> {
self.storage.clone()
}
fn runner(&self) -> Option<OneOrMany<Name>> {
self.runner.clone()
}
fn resolver(&self) -> Option<OneOrMany<Name>> {
self.resolver.clone()
}
fn event(&self) -> Option<OneOrMany<Name>> {
self.event.clone()
}
fn event_stream(&self) -> Option<OneOrMany<Name>> {
self.event_stream.clone()
}
fn event_stream_deprecated(&self) -> Option<Name> {
None
}
fn capability_type(&self) -> &'static str {
if self.service.is_some() {
"service"
} else if self.protocol.is_some() {
"protocol"
} else if self.directory.is_some() {
"directory"
} else if self.storage.is_some() {
"storage"
} else if self.runner.is_some() {
"runner"
} else if self.resolver.is_some() {
"resolver"
} else if self.event.is_some() {
"event"
} else if self.event_stream.is_some() {
"event_stream"
} else {
panic!("Missing capability name")
}
}
fn decl_type(&self) -> &'static str {
"offer"
}
fn supported(&self) -> &[&'static str] {
&[
"service",
"protocol",
"directory",
"storage",
"runner",
"resolver",
"event",
"event_stream",
]
}
}
impl AsClause for Offer {
fn r#as(&self) -> Option<&Name> {
self.r#as.as_ref()
}
}
impl PathClause for Offer {
fn path(&self) -> Option<&Path> {
None
}
}
impl FilterClause for Offer {
fn filter(&self) -> Option<&Map<String, Value>> {
self.filter.as_ref()
}
}
impl RightsClause for Offer {
fn rights(&self) -> Option<&Rights> {
self.rights.as_ref()
}
}
impl FromClause for RunnerRegistration {
fn from_(&self) -> OneOrMany<AnyRef<'_>> {
OneOrMany::One(AnyRef::from(&self.from))
}
}
impl FromClause for ResolverRegistration {
fn from_(&self) -> OneOrMany<AnyRef<'_>> {
OneOrMany::One(AnyRef::from(&self.from))
}
}
fn one_or_many_from_impl<'a, T>(from: &'a OneOrMany<T>) -> OneOrMany<AnyRef<'a>>
where
AnyRef<'a>: From<&'a T>,
T: 'a,
{
let r = match from {
OneOrMany::One(r) => OneOrMany::One(r.into()),
OneOrMany::Many(v) => OneOrMany::Many(v.into_iter().map(|r| r.into()).collect()),
};
r.into()
}
pub fn alias_or_name(alias: Option<&Name>, name: &Name) -> Name {
alias.unwrap_or(name).clone()
}
pub fn alias_or_path(alias: Option<&Path>, path: &Path) -> Path {
alias.unwrap_or(path).clone()
}
pub fn format_cml(buffer: &str, file: &std::path::Path) -> Result<Vec<u8>, Error> {
let general_order = PathOption::PropertyNameOrder(vec![
"name",
"url",
"startup",
"environment",
"durability",
"service",
"protocol",
"directory",
"storage",
"runner",
"resolver",
"event",
"event_stream",
"from",
"as",
"to",
"rights",
"path",
"subdir",
"filter",
"dependency",
"extends",
"runners",
"resolvers",
"debug",
]);
let options = FormatOptions {
collapse_containers_of_one: true,
sort_array_items: true, // but use options_by_path to turn this off for program args
options_by_path: hashmap! {
"/*" => hashset! {
PathOption::PropertyNameOrder(vec![
"include",
"program",
"children",
"collections",
"capabilities",
"use",
"offer",
"expose",
"environments",
"facets",
])
},
"/*/program" => hashset! {
PathOption::CollapseContainersOfOne(false),
PathOption::PropertyNameOrder(vec![
"runner",
"binary",
"args",
]),
},
"/*/program/*" => hashset! {
PathOption::SortArrayItems(false),
},
"/*/*/*" => hashset! {
general_order.clone()
},
"/*/*/*/*/*" => hashset! {
general_order
},
},
..Default::default()
};
json5format::format(buffer, Some(file.to_string_lossy().to_string()), Some(options))
.map_err(|e| Error::json5(e, file))
}
#[cfg(test)]
mod tests {
use super::*;
use {
assert_matches::assert_matches,
cm_json::{self, Error as JsonError},
error::Error,
serde_json::{self, json},
serde_json5,
std::path::Path,
test_case::test_case,
};
// Exercise reference parsing tests on `OfferFromRef` because it contains every reference
// subtype.
#[test]
fn test_parse_named_reference() {
assert_matches!("#some-child".parse::<OfferFromRef>(), Ok(OfferFromRef::Named(name)) if name == "some-child");
assert_matches!("#A".parse::<OfferFromRef>(), Ok(OfferFromRef::Named(name)) if name == "A");
assert_matches!("#7".parse::<OfferFromRef>(), Ok(OfferFromRef::Named(name)) if name == "7");
assert_matches!("#_".parse::<OfferFromRef>(), Ok(OfferFromRef::Named(name)) if name == "_");
assert_matches!("#-".parse::<OfferFromRef>(), Err(_));
assert_matches!("#.".parse::<OfferFromRef>(), Err(_));
assert_matches!("#".parse::<OfferFromRef>(), Err(_));
assert_matches!("some-child".parse::<OfferFromRef>(), Err(_));
}
#[test]
fn test_parse_reference_test() {
assert_matches!("parent".parse::<OfferFromRef>(), Ok(OfferFromRef::Parent));
assert_matches!("framework".parse::<OfferFromRef>(), Ok(OfferFromRef::Framework));
assert_matches!("self".parse::<OfferFromRef>(), Ok(OfferFromRef::Self_));
assert_matches!("#child".parse::<OfferFromRef>(), Ok(OfferFromRef::Named(name)) if name == "child");
assert_matches!("invalid".parse::<OfferFromRef>(), Err(_));
assert_matches!("#invalid-child^".parse::<OfferFromRef>(), Err(_));
}
fn parse_as_ref(input: &str) -> Result<OfferFromRef, JsonError> {
serde_json::from_value::<OfferFromRef>(cm_json::from_json_str(
input,
&Path::new("test.cml"),
)?)
.map_err(|e| JsonError::parse(format!("{}", e), None, None))
}
#[test]
fn test_deserialize_ref() -> Result<(), JsonError> {
assert_matches!(parse_as_ref("\"self\""), Ok(OfferFromRef::Self_));
assert_matches!(parse_as_ref("\"parent\""), Ok(OfferFromRef::Parent));
assert_matches!(parse_as_ref("\"#child\""), Ok(OfferFromRef::Named(name)) if name == "child");
assert_matches!(parse_as_ref(r#""invalid""#), Err(_));
Ok(())
}
macro_rules! test_parse_rights {
(
$(
($input:expr, $expected:expr),
)+
) => {
#[test]
fn parse_rights() {
$(
parse_rights_test($input, $expected);
)+
}
}
}
fn parse_rights_test(input: &str, expected: Right) {
let r: Right = serde_json5::from_str(&format!("\"{}\"", input)).expect("invalid json");
assert_eq!(r, expected);
}
test_parse_rights! {
("connect", Right::Connect),
("enumerate", Right::Enumerate),
("execute", Right::Execute),
("get_attributes", Right::GetAttributes),
("modify_directory", Right::ModifyDirectory),
("read_bytes", Right::ReadBytes),
("traverse", Right::Traverse),
("update_attributes", Right::UpdateAttributes),
("write_bytes", Right::WriteBytes),
("r*", Right::ReadAlias),
("w*", Right::WriteAlias),
("x*", Right::ExecuteAlias),
("rw*", Right::ReadWriteAlias),
("rx*", Right::ReadExecuteAlias),
}
macro_rules! test_expand_rights {
(
$(
($input:expr, $expected:expr),
)+
) => {
#[test]
fn expand_rights() {
$(
expand_rights_test($input, $expected);
)+
}
}
}
fn expand_rights_test(input: Right, expected: Vec<fio::Operations>) {
assert_eq!(input.expand(), expected);
}
test_expand_rights! {
(Right::Connect, vec![fio::Operations::CONNECT]),
(Right::Enumerate, vec![fio::Operations::ENUMERATE]),
(Right::Execute, vec![fio::Operations::EXECUTE]),
(Right::GetAttributes, vec![fio::Operations::GET_ATTRIBUTES]),
(Right::ModifyDirectory, vec![fio::Operations::MODIFY_DIRECTORY]),
(Right::ReadBytes, vec![fio::Operations::READ_BYTES]),
(Right::Traverse, vec![fio::Operations::TRAVERSE]),
(Right::UpdateAttributes, vec![fio::Operations::UPDATE_ATTRIBUTES]),
(Right::WriteBytes, vec![fio::Operations::WRITE_BYTES]),
(Right::ReadAlias, vec![
fio::Operations::CONNECT,
fio::Operations::ENUMERATE,
fio::Operations::TRAVERSE,
fio::Operations::READ_BYTES,
fio::Operations::GET_ATTRIBUTES,
]),
(Right::WriteAlias, vec![
fio::Operations::CONNECT,
fio::Operations::ENUMERATE,
fio::Operations::TRAVERSE,
fio::Operations::WRITE_BYTES,
fio::Operations::MODIFY_DIRECTORY,
fio::Operations::UPDATE_ATTRIBUTES,
]),
(Right::ExecuteAlias, vec![
fio::Operations::CONNECT,
fio::Operations::ENUMERATE,
fio::Operations::TRAVERSE,
fio::Operations::EXECUTE,
]),
(Right::ReadWriteAlias, vec![
fio::Operations::CONNECT,
fio::Operations::ENUMERATE,
fio::Operations::TRAVERSE,
fio::Operations::READ_BYTES,
fio::Operations::WRITE_BYTES,
fio::Operations::MODIFY_DIRECTORY,
fio::Operations::GET_ATTRIBUTES,
fio::Operations::UPDATE_ATTRIBUTES,
]),
(Right::ReadExecuteAlias, vec![
fio::Operations::CONNECT,
fio::Operations::ENUMERATE,
fio::Operations::TRAVERSE,
fio::Operations::READ_BYTES,
fio::Operations::GET_ATTRIBUTES,
fio::Operations::EXECUTE,
]),
}
#[test]
fn test_deny_unknown_fields() {
assert_matches!(serde_json5::from_str::<Document>("{ unknown: \"\" }"), Err(_));
assert_matches!(serde_json5::from_str::<Environment>("{ unknown: \"\" }"), Err(_));
assert_matches!(serde_json5::from_str::<RunnerRegistration>("{ unknown: \"\" }"), Err(_));
assert_matches!(serde_json5::from_str::<ResolverRegistration>("{ unknown: \"\" }"), Err(_));
assert_matches!(serde_json5::from_str::<Use>("{ unknown: \"\" }"), Err(_));
assert_matches!(serde_json5::from_str::<Expose>("{ unknown: \"\" }"), Err(_));
assert_matches!(serde_json5::from_str::<Offer>("{ unknown: \"\" }"), Err(_));
assert_matches!(serde_json5::from_str::<Capability>("{ unknown: \"\" }"), Err(_));
assert_matches!(serde_json5::from_str::<Child>("{ unknown: \"\" }"), Err(_));
assert_matches!(serde_json5::from_str::<Collection>("{ unknown: \"\" }"), Err(_));
}
// TODO: Use Default::default() instead
fn empty_offer() -> Offer {
Offer {
service: None,
protocol: None,
directory: None,
storage: None,
runner: None,
resolver: None,
event: None,
from: OneOrMany::One(OfferFromRef::Self_),
to: OneOrMany::Many(vec![]),
r#as: None,
rights: None,
subdir: None,
dependency: None,
filter: None,
event_stream: None,
scope: None,
availability: None,
source_availability: None,
}
}
fn empty_use() -> Use {
Use {
service: None,
protocol: None,
scope: None,
directory: None,
storage: None,
from: None,
path: None,
r#as: None,
rights: None,
subdir: None,
event: None,
event_stream_deprecated: None,
event_stream: None,
filter: None,
subscriptions: None,
dependency: None,
availability: None,
}
}
#[test]
fn test_capability_id() -> Result<(), Error> {
// service
assert_eq!(
CapabilityId::from_offer_expose(&Offer {
service: Some(OneOrMany::One("a".parse().unwrap())),
..empty_offer()
},)?,
vec![CapabilityId::Service("a".parse().unwrap())]
);
assert_eq!(
CapabilityId::from_offer_expose(&Offer {
service: Some(OneOrMany::Many(vec!["a".parse().unwrap(), "b".parse().unwrap()],)),
..empty_offer()
},)?,
vec![
CapabilityId::Service("a".parse().unwrap()),
CapabilityId::Service("b".parse().unwrap())
]
);
assert_eq!(
CapabilityId::from_use(&Use {
service: Some(OneOrMany::One("a".parse().unwrap())),
..empty_use()
},)?,
vec![CapabilityId::UsedService("/svc/a".parse().unwrap())]
);
assert_eq!(
CapabilityId::from_use(&Use {
service: Some(OneOrMany::Many(vec!["a".parse().unwrap(), "b".parse().unwrap(),],)),
..empty_use()
},)?,
vec![
CapabilityId::UsedService("/svc/a".parse().unwrap()),
CapabilityId::UsedService("/svc/b".parse().unwrap())
]
);
assert_eq!(
CapabilityId::from_use(&Use {
event_stream: Some(OneOrMany::One(Name::new("test".to_string()).unwrap())),
path: Some(cm_types::Path::new("/svc/myevent".to_string()).unwrap()),
..empty_use()
},)?,
vec![CapabilityId::EventStream("/svc/myevent".parse().unwrap()),]
);
assert_eq!(
CapabilityId::from_use(&Use {
event_stream: Some(OneOrMany::One(Name::new("test".to_string()).unwrap())),
..empty_use()
},)?,
vec![CapabilityId::EventStream("/svc/fuchsia.component.EventStream".parse().unwrap()),]
);
assert_eq!(
CapabilityId::from_use(&Use {
service: Some(OneOrMany::One("a".parse().unwrap())),
path: Some("/b".parse().unwrap()),
..empty_use()
},)?,
vec![CapabilityId::UsedService("/b".parse().unwrap())]
);
// protocol
assert_eq!(
CapabilityId::from_offer_expose(&Offer {
protocol: Some(OneOrMany::One("a".parse().unwrap())),
..empty_offer()
},)?,
vec![CapabilityId::Protocol("a".parse().unwrap())]
);
assert_eq!(
CapabilityId::from_offer_expose(&Offer {
protocol: Some(OneOrMany::Many(vec!["a".parse().unwrap(), "b".parse().unwrap()],)),
..empty_offer()
},)?,
vec![
CapabilityId::Protocol("a".parse().unwrap()),
CapabilityId::Protocol("b".parse().unwrap())
]
);
assert_eq!(
CapabilityId::from_use(&Use {
protocol: Some(OneOrMany::One("a".parse().unwrap())),
..empty_use()
},)?,
vec![CapabilityId::UsedProtocol("/svc/a".parse().unwrap())]
);
assert_eq!(
CapabilityId::from_use(&Use {
protocol: Some(OneOrMany::Many(vec!["a".parse().unwrap(), "b".parse().unwrap(),],)),
..empty_use()
},)?,
vec![
CapabilityId::UsedProtocol("/svc/a".parse().unwrap()),
CapabilityId::UsedProtocol("/svc/b".parse().unwrap())
]
);
assert_eq!(
CapabilityId::from_use(&Use {
protocol: Some(OneOrMany::One("a".parse().unwrap())),
path: Some("/b".parse().unwrap()),
..empty_use()
},)?,
vec![CapabilityId::UsedProtocol("/b".parse().unwrap())]
);
// directory
assert_eq!(
CapabilityId::from_offer_expose(&Offer {
directory: Some(OneOrMany::One("a".parse().unwrap())),
..empty_offer()
},)?,
vec![CapabilityId::Directory("a".parse().unwrap())]
);
assert_eq!(
CapabilityId::from_offer_expose(&Offer {
directory: Some(OneOrMany::Many(vec!["a".parse().unwrap(), "b".parse().unwrap()])),
..empty_offer()
},)?,
vec![
CapabilityId::Directory("a".parse().unwrap()),
CapabilityId::Directory("b".parse().unwrap()),
]
);
assert_eq!(
CapabilityId::from_use(&Use {
directory: Some("a".parse().unwrap()),
path: Some("/b".parse().unwrap()),
..empty_use()
},)?,
vec![CapabilityId::UsedDirectory("/b".parse().unwrap())]
);
// storage
assert_eq!(
CapabilityId::from_offer_expose(&Offer {
storage: Some(OneOrMany::One("cache".parse().unwrap())),
..empty_offer()
},)?,
vec![CapabilityId::Storage("cache".parse().unwrap())],
);
assert_eq!(
CapabilityId::from_offer_expose(&Offer {
storage: Some(OneOrMany::Many(vec!["a".parse().unwrap(), "b".parse().unwrap()])),
..empty_offer()
},)?,
vec![
CapabilityId::Storage("a".parse().unwrap()),
CapabilityId::Storage("b".parse().unwrap()),
]
);
assert_eq!(
CapabilityId::from_use(&Use {
storage: Some("a".parse().unwrap()),
path: Some("/b".parse().unwrap()),
..empty_use()
},)?,
vec![CapabilityId::UsedStorage("/b".parse().unwrap())]
);
// "as" aliasing.
assert_eq!(
CapabilityId::from_offer_expose(&Offer {
service: Some(OneOrMany::One("a".parse().unwrap())),
r#as: Some("b".parse().unwrap()),
..empty_offer()
},)?,
vec![CapabilityId::Service("b".parse().unwrap())]
);
// Error case.
assert_matches!(CapabilityId::from_offer_expose(&empty_offer()), Err(_));
Ok(())
}
fn document(contents: serde_json::Value) -> Document {
serde_json5::from_str::<Document>(&contents.to_string()).unwrap()
}
#[test]
fn test_includes() {
assert_eq!(document(json!({})).includes(), Vec::<String>::new());
assert_eq!(document(json!({ "include": []})).includes(), Vec::<String>::new());
assert_eq!(
document(json!({ "include": [ "foo.cml", "bar.cml" ]})).includes(),
vec!["foo.cml", "bar.cml"]
);
}
#[test]
fn test_merge_same_section() {
let mut some = document(json!({ "use": [{ "protocol": "foo" }] }));
let mut other = document(json!({ "use": [{ "protocol": "bar" }] }));
some.merge_from(&mut other, &Path::new("some/path")).unwrap();
let uses = some.r#use.as_ref().unwrap();
assert_eq!(uses.len(), 2);
assert_eq!(
uses[0].protocol.as_ref().unwrap(),
&OneOrMany::One("foo".parse::<Name>().unwrap())
);
assert_eq!(
uses[1].protocol.as_ref().unwrap(),
&OneOrMany::One("bar".parse::<Name>().unwrap())
);
}
#[test]
fn test_merge_different_sections() {
let mut some = document(json!({ "use": [{ "protocol": "foo" }] }));
let mut other = document(json!({ "expose": [{ "protocol": "bar", "from": "self" }] }));
some.merge_from(&mut other, &Path::new("some/path")).unwrap();
let uses = some.r#use.as_ref().unwrap();
let exposes = some.r#expose.as_ref().unwrap();
assert_eq!(uses.len(), 1);
assert_eq!(exposes.len(), 1);
assert_eq!(
uses[0].protocol.as_ref().unwrap(),
&OneOrMany::One("foo".parse::<Name>().unwrap())
);
assert_eq!(
exposes[0].protocol.as_ref().unwrap(),
&OneOrMany::One("bar".parse::<Name>().unwrap())
);
}
#[test]
fn test_merge_from_other_config() {
let mut some = document(json!({}));
let mut other = document(json!({ "config": { "bar": { "type": "bool" } } }));
some.merge_from(&mut other, &path::Path::new("some/path")).unwrap();
let expected = document(json!({ "config": { "bar": { "type": "bool" } } }));
assert_eq!(some.config, expected.config);
}
#[test]
fn test_merge_from_some_config() {
let mut some = document(json!({ "config": { "bar": { "type": "bool" } } }));
let mut other = document(json!({}));
some.merge_from(&mut other, &path::Path::new("some/path")).unwrap();
let expected = document(json!({ "config": { "bar": { "type": "bool" } } }));
assert_eq!(some.config, expected.config);
}
#[test]
fn test_merge_from_config_error() {
let mut some = document(json!({ "config": { "foo": { "type": "bool" } } }));
let mut other = document(json!({ "config": { "bar": { "type": "bool" } } }));
assert_matches::assert_matches!(
some.merge_from(&mut other, &path::Path::new("some/path")),
Err(Error::Validate { schema_name: None, err, .. })
if err == "multiple config schemas found (last import: some/path). See https://fxbug.dev/93679 for more information"
);
}
#[test]
fn test_merge_from_program() {
let mut some = document(json!({ "program": { "binary": "bin/hello_world" } }));
let mut other = document(json!({ "program": { "runner": "elf" } }));
some.merge_from(&mut other, &Path::new("some/path")).unwrap();
let expected =
document(json!({ "program": { "binary": "bin/hello_world", "runner": "elf" } }));
assert_eq!(some.program, expected.program);
}
#[test]
fn test_merge_from_program_without_runner() {
let mut some =
document(json!({ "program": { "binary": "bin/hello_world", "runner": "elf" } }));
// fxbug.dev/79951: merging with a document that doesn't have a runner doesn't override the
// runner that we already have assigned.
let mut other = document(json!({ "program": {} }));
some.merge_from(&mut other, &Path::new("some/path")).unwrap();
let expected =
document(json!({ "program": { "binary": "bin/hello_world", "runner": "elf" } }));
assert_eq!(some.program, expected.program);
}
#[test]
fn test_merge_from_program_overlapping_runner() {
// It's ok to merge `program.runner = "elf"` with `program.runner = "elf"`.
let mut some =
document(json!({ "program": { "binary": "bin/hello_world", "runner": "elf" } }));
let mut other = document(json!({ "program": { "runner": "elf" } }));
some.merge_from(&mut other, &Path::new("some/path")).unwrap();
let expected =
document(json!({ "program": { "binary": "bin/hello_world", "runner": "elf" } }));
assert_eq!(some.program, expected.program);
}
#[test_case(
document(json!({ "program": { "runner": "elf" } })),
document(json!({ "program": { "runner": "fle" } })),
"runner"
; "when_runner_conflicts"
)]
#[test_case(
document(json!({ "program": { "binary": "bin/hello_world" } })),
document(json!({ "program": { "binary": "bin/hola_mundo" } })),
"binary"
; "when_binary_conflicts"
)]
#[test_case(
document(json!({ "program": { "args": ["a".to_owned()] } })),
document(json!({ "program": { "args": ["b".to_owned()] } })),
"args"
; "when_args_conflicts"
)]
fn test_merge_from_program_error(mut some: Document, mut other: Document, field: &str) {
assert_matches::assert_matches!(
some.merge_from(&mut other, &path::Path::new("some/path")),
Err(Error::Validate { schema_name: None, err, .. })
if err == format!("manifest include had a conflicting `program.{}`: some/path", field)
);
}
#[test_case(
document(json!({ "facets": { "my.key": "my.value" } })),
document(json!({ "facets": { "other.key": "other.value" } })),
document(json!({ "facets": { "my.key": "my.value", "other.key": "other.value" } }))
; "two separate keys"
)]
#[test_case(
document(json!({ "facets": { "my.key": "my.value" } })),
document(json!({ "facets": {} })),
document(json!({ "facets": { "my.key": "my.value" } }))
; "empty other facet"
)]
#[test_case(
document(json!({ "facets": {} })),
document(json!({ "facets": { "other.key": "other.value" } })),
document(json!({ "facets": { "other.key": "other.value" } }))
; "empty my facet"
)]
#[test_case(
document(json!({ "facets": { "key": { "type": "some_type" } } })),
document(json!({ "facets": { "key": { "runner": "some_runner"} } })),
document(json!({ "facets": { "key": { "type": "some_type", "runner": "some_runner" } } }))
; "nested facet key"
)]
#[test_case(
document(json!({ "facets": { "key": { "type": "some_type", "nested_key": { "type": "new type" }}}})),
document(json!({ "facets": { "key": { "nested_key": { "runner": "some_runner" }} } })),
document(json!({ "facets": { "key": { "type": "some_type", "nested_key": { "runner": "some_runner", "type": "new type" }}}}))
; "double nested facet key"
)]
fn test_merge_from_facets(mut my: Document, mut other: Document, expected: Document) {
my.merge_from(&mut other, &Path::new("some/path")).unwrap();
assert_eq!(my.facets, expected.facets);
}
#[test_case(
document(json!({ "facets": { "key": "my.value" }})),
document(json!({ "facets": { "key": "other.value" }})),
"facets.key"
; "conflict first level keys"
)]
#[test_case(
document(json!({ "facets": { "key": {"type": "cts" }}})),
document(json!({ "facets": { "key": {"type": "system" }}})),
"facets.key.type"
; "conflict second level keys"
)]
#[test_case(
document(json!({ "facets": { "key": {"type": {"key": "value" }}}})),
document(json!({ "facets": { "key": {"type": "system" }}})),
"facets.key.type"
; "incompatible self nested type"
)]
#[test_case(
document(json!({ "facets": { "key": {"type": "system" }}})),
document(json!({ "facets": { "key": {"type": {"key": "value" }}}})),
"facets.key.type"
; "incompatible other nested type"
)]
#[test_case(
document(json!({ "facets": { "key": {"type": {"key": "my.value" }}}})),
document(json!({ "facets": { "key": {"type": {"key": "some.value" }}}})),
"facets.key.type.key"
; "conflict third level keys"
)]
fn test_merge_from_facet_error(mut my: Document, mut other: Document, field: &str) {
assert_matches::assert_matches!(
my.merge_from(&mut other, &path::Path::new("some/path")),
Err(Error::Validate { schema_name: None, err, .. })
if err == format!("manifest include had a conflicting `{}`: some/path", field)
);
}
#[test_case(
document(json!({ "use": [{ "protocol": "foo.bar.Baz", "from": "self"}]})),
document(json!({ "use": [{ "protocol": ["foo.bar.Baz", "some.other.Protocol"], "from": "self"}]})),
document(json!({ "use": [{ "protocol": "foo.bar.Baz", "from": "self" },{"protocol": "some.other.Protocol", "from": "self"}]}))
; "merge duplicate protocols in use clause"
)]
#[test_case(
document(json!({ "use": [{ "service": "foo.bar.Baz", "from": "self"}]})),
document(json!({ "use": [{ "service": ["foo.bar.Baz", "some.other.Service"], "from": "self"}]})),
document(json!({ "use": [{ "service": "foo.bar.Baz", "from": "self" },{"service": "some.other.Service", "from": "self"}]}))
; "merge duplicate capabilities service use clause"
)]
#[test_case(
document(json!({ "use": [{ "event": "EventFoo", "from": "self"}]})),
document(json!({ "use": [{ "event": ["EventFoo", "EventBar"], "from": "self"}]})),
document(json!({ "use": [{ "event": "EventFoo", "from": "self" },{"event": "EventBar", "from": "self"}]}))
; "merge duplicate capabilities events use clause"
)]
#[test_case(
document(json!({ "offer": [{ "protocol": "foo.bar.Baz", "from": "self", "to": "#elements"}], "collections" :[{"name": "elements", "durability": "transient" }]})),
document(json!({ "offer": [{ "protocol": ["foo.bar.Baz", "some.other.Protocol"], "from": "self", "to": "#elements"}], "collections":[{"name": "elements", "durability": "transient"}]})),
document(json!({ "offer": [{ "protocol": "foo.bar.Baz", "from": "self", "to": "#elements" },{"protocol": "some.other.Protocol", "from": "self", "to": "#elements"}], "collections":[{"name": "elements", "durability": "transient"}]}))
; "merge duplicate protocols in offer clause"
)]
#[test_case(
document(json!({ "offer": [{ "service": "foo.bar.Baz", "from": "self", "to": "#elements"}], "collections" :[{"name": "elements", "durability": "transient" }]})),
document(json!({ "offer": [{ "service": ["foo.bar.Baz", "some.other.Service"], "from": "self", "to": "#elements"}], "collections":[{"name": "elements", "durability": "transient"}]})),
document(json!({ "offer": [{ "service": "foo.bar.Baz", "from": "self", "to": "#elements" },{"service": "some.other.Service", "from": "self", "to": "#elements"}], "collections":[{"name": "elements", "durability": "transient"}]}))
; "merge duplicate capabilities service offer clause"
)]
#[test_case(
document(json!({ "offer": [{ "event": "EventFoo", "from": "self", "to": "#elements"}], "collections" :[{"name": "elements", "durability": "transient" }]})),
document(json!({ "offer": [{ "event": ["EventFoo", "EventBar"], "from": "self", "to": "#elements"}], "collections":[{"name": "elements", "durability": "transient"}]})),
document(json!({ "offer": [{ "event": "EventFoo", "from": "self", "to": "#elements" },{"event": "EventBar", "from": "self", "to": "#elements"}], "collections":[{"name": "elements", "durability": "transient"}]}))
; "merge duplicate capabilities events offer clause"
)]
#[test_case(
document(json!({ "expose": [{ "protocol": "foo.bar.Baz", "from": "self"}]})),
document(json!({ "expose": [{ "protocol": ["foo.bar.Baz", "some.other.Protocol"], "from": "self"}]})),
document(json!({ "expose": [{ "protocol": "foo.bar.Baz", "from": "self" },{"protocol": "some.other.Protocol", "from": "self"}]}))
; "merge duplicate protocols in expose clause"
)]
#[test_case(
document(json!({ "expose": [{ "service": "foo.bar.Baz", "from": "self"}]})),
document(json!({ "expose": [{ "service": ["foo.bar.Baz", "some.other.Service"], "from": "self"}]})),
document(json!({ "expose": [{ "service": "foo.bar.Baz", "from": "self" },{"service": "some.other.Service", "from": "self"}]}))
; "merge duplicate service capabilities in expose clause"
)]
#[test_case(
document(json!({ "capabilities": [{ "protocol": "foo.bar.Baz", "from": "self"}]})),
document(json!({ "capabilities": [{ "protocol": ["foo.bar.Baz", "some.other.Protocol"], "from": "self"}]})),
document(json!({ "capabilities": [{ "protocol": "foo.bar.Baz", "from": "self" },{"protocol": "some.other.Protocol", "from": "self"}]}))
; "merge duplicate protocols in capabilities clause"
)]
#[test_case(
document(json!({ "capabilities": [{ "service": "foo.bar.Baz", "from": "self"}]})),
document(json!({ "capabilities": [{ "service": ["foo.bar.Baz", "some.other.Service"], "from": "self"}]})),
document(json!({ "capabilities": [{ "service": "foo.bar.Baz", "from": "self" },{"service": "some.other.Service", "from": "self"}]}))
; "merge duplicate services in capabilities clause"
)]
fn test_merge_from_duplicate_capability(
mut my: Document,
mut other: Document,
result: Document,
) {
my.merge_from(&mut other, &path::Path::new("some/path")).unwrap();
assert_eq!(my, result);
}
#[test_case(&Right::Connect; "connect right")]
#[test_case(&Right::Enumerate; "enumerate right")]
#[test_case(&Right::Execute; "execute right")]
#[test_case(&Right::GetAttributes; "getattr right")]
#[test_case(&Right::ModifyDirectory; "modifydir right")]
#[test_case(&Right::ReadBytes; "readbytes right")]
#[test_case(&Right::Traverse; "traverse right")]
#[test_case(&Right::UpdateAttributes; "updateattrs right")]
#[test_case(&Right::WriteBytes; "writebytes right")]
#[test_case(&Right::ReadAlias; "r right")]
#[test_case(&Right::WriteAlias; "w right")]
#[test_case(&Right::ExecuteAlias; "x right")]
#[test_case(&Right::ReadWriteAlias; "rw right")]
#[test_case(&Right::ReadExecuteAlias; "rx right")]
#[test_case(&OfferFromRef::Self_; "offer from self")]
#[test_case(&OfferFromRef::Parent; "offer from parent")]
#[test_case(&OfferFromRef::Named(Name::new("child".to_string()).unwrap()); "offer from named")]
#[test_case(
&document(json!({}));
"empty document"
)]
#[test_case(
&document(json!({ "use": [{ "protocol": "foo.bar.Baz", "from": "self"}]}));
"use one from self"
)]
#[test_case(
&document(json!({ "use": [{ "protocol": ["foo.bar.Baz", "some.other.Protocol"], "from": "self"}]}));
"use multiple from self"
)]
#[test_case(
&document(json!({ "use": [{ "event": "EventFoo", "from": "self"}]}));
"use event from self"
)]
#[test_case(
&document(json!({ "use": [{ "event": ["EventFoo", "EventBar"], "from": "self"}]}));
"use events from self"
)]
#[test_case(
&document(json!({
"offer": [{ "protocol": "foo.bar.Baz", "from": "self", "to": "#elements"}],
"collections" :[{"name": "elements", "durability": "transient" }]
}));
"offer from self to collection"
)]
#[test_case(
&document(json!({
"offer": [
{ "service": "foo.bar.Baz", "from": "self", "to": "#elements" },
{ "service": "some.other.Service", "from": "self", "to": "#elements"},
],
"collections":[ {"name": "elements", "durability": "transient"} ]}));
"service offers"
)]
#[test_case(
&document(json!({
"offer": [{ "event": "EventFoo", "from": "self", "to": "#elements"}],
"collections" :[{"name": "elements", "durability": "transient" }]}));
"offer event to collection"
)]
#[test_case(
&document(json!({ "expose": [{ "protocol": ["foo.bar.Baz", "some.other.Protocol"], "from": "self"}]}));
"expose protocols from self"
)]
#[test_case(
&document(json!({ "expose": [{ "service": ["foo.bar.Baz", "some.other.Service"], "from": "self"}]}));
"expose service from self"
)]
#[test_case(
&document(json!({ "capabilities": [{ "protocol": "foo.bar.Baz", "from": "self"}]}));
"capabilities from self"
)]
#[test_case(
&document(json!({ "facets": { "my.key": "my.value" } }));
"facets"
)]
#[test_case(
&document(json!({ "program": { "binary": "bin/hello_world", "runner": "elf" } }));
"elf runner program"
)]
fn serialize_roundtrips<T>(val: &T)
where
T: serde::de::DeserializeOwned + Serialize + PartialEq + std::fmt::Debug,
{
let raw = serde_json::to_string(val).expect("serializing `val` should work");
let parsed: T =
serde_json::from_str(&raw).expect("must be able to parse back serialized value");
assert_eq!(val, &parsed, "parsed value must equal original value");
}
}