blob: 414c51f4ae3581c0fb67400dc66fa848fd3a4f96 [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.
pub mod error;
pub mod one_or_many;
pub mod translate;
use {
crate::error::Error,
crate::one_or_many::OneOrMany,
cml_macro::{CheckedVec, OneOrMany, Reference},
fidl_fuchsia_io2 as fio2,
lazy_static::lazy_static,
serde::{de, Deserialize},
serde_json::{Map, Value},
std::{collections::HashMap, fmt, path},
};
pub use cm_types::{
DependencyType, Durability, Name, NameOrPath, ParseError, Path, RelativePath, StartupMode, Url,
};
lazy_static! {
static ref DEFAULT_EVENT_STREAM_PATH: Path = "/svc/fuchsia.sys2.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),
Storage(Name),
Runner(Name),
Resolver(Name),
Event(Name),
EventStream(Path),
}
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::UsedDirectory(_) => "directory",
CapabilityId::UsedProtocol(_) => "protocol",
CapabilityId::Storage(_) => "storage",
CapabilityId::Runner(_) => "runner",
CapabilityId::Resolver(_) => "resolver",
CapabilityId::Event(_) => "event",
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())),
_ => None,
}
}
/// Given a Capability, Use, 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.
// TODO: Break up this function by clause type, it's trying to do too much. This will be easier
// to do once name-based capabilities have fully replaced path-based.
pub fn from_clause<T>(
clause: &T,
clause_type: RoutingClauseType,
) -> Result<Vec<CapabilityId>, Error>
where
T: CapabilityClause + AsClause + PathClause + FilterClause + fmt::Debug,
{
// For directory/service/runner types, return the source name,
// using the "as" clause to rename if neccessary.
// TODO: Validate that exactly one of these is set.
let alias = clause.r#as();
let path = clause.path();
if let Some(n) = clause.service().as_ref() {
return match clause_type {
RoutingClauseType::Use => {
let path = match path {
Some(path) => path.clone(),
None => format!("/svc/{}", n).parse().unwrap(),
};
Ok(vec![CapabilityId::UsedService(path)])
}
_ => Ok(vec![CapabilityId::Service(alias_or_name(alias, n))]),
};
} else if let Some(OneOrMany::One(protocol)) = clause.protocol() {
return match (clause_type, protocol) {
(RoutingClauseType::Capability, protocol) => {
Ok(vec![CapabilityId::Protocol(protocol)])
}
(RoutingClauseType::Use, protocol) => {
let path = match path {
Some(path) => path.clone(),
None => format!("/svc/{}", protocol).parse().unwrap(),
};
Ok(vec![CapabilityId::UsedProtocol(path)])
}
(_, protocol) => Ok(vec![CapabilityId::Protocol(alias_or_name(alias, &protocol))]),
};
} else if let Some(OneOrMany::Many(protocols)) = clause.protocol() {
return match clause_type {
RoutingClauseType::Use if protocols.len() == 1 => {
let protocol = &protocols[0];
let path = match path {
Some(path) => path.clone(),
None => format!("/svc/{}", protocol).parse().unwrap(),
};
Ok(vec![CapabilityId::UsedProtocol(path)])
}
RoutingClauseType::Use => {
if alias.is_some() {
return Err(Error::validate(
"\"as\" field can only be specified when one `protocol` is supplied.",
));
}
if path.is_some() {
return Err(Error::validate(
"\"path\" field can only be specified when one `protocol` is supplied.",
));
}
Ok(protocols
.iter()
.map(|protocol: &Name| {
let protocol: Path = format!("/svc/{}", protocol).parse().unwrap();
CapabilityId::UsedProtocol(protocol)
})
.collect())
}
RoutingClauseType::Capability if protocols.len() == 1 => {
Ok(vec![CapabilityId::Protocol(protocols[0].clone())])
}
RoutingClauseType::Capability => {
if path.is_some() {
return Err(Error::validate(
"\"path\" field can only be specified when one `protocol` is supplied.",
));
}
Ok(protocols
.iter()
.map(|protocol: &Name| CapabilityId::Protocol(protocol.clone()))
.collect())
}
_ if protocols.len() == 1 => {
Ok(vec![CapabilityId::Protocol(alias_or_name(alias, &protocols[0]))])
}
_ => {
if alias.is_some() {
return Err(Error::validate(
"\"as\" field can only be specified when one `protocol` is supplied.",
));
}
Ok(protocols
.iter()
.map(|protocol: &Name| CapabilityId::Protocol(protocol.clone()))
.collect())
}
};
} else if let Some(directory) = clause.directory() {
return match (clause_type, directory) {
(RoutingClauseType::Capability, directory) => {
Ok(vec![CapabilityId::Directory(directory)])
}
(RoutingClauseType::Use, _) => {
if path.is_none() {
return Err(Error::validate(
"\"path\" field should be present for `use directory`.",
));
}
Ok(vec![CapabilityId::UsedDirectory(path.unwrap().clone())])
}
(_, directory) => {
Ok(vec![CapabilityId::Directory(alias_or_name(alias, &directory))])
}
};
} else if let Some(n) = clause.storage().as_ref() {
return Ok(vec![CapabilityId::Storage(alias_or_name(alias, n))]);
} else if let Some(n) = clause.runner().as_ref() {
return Ok(vec![CapabilityId::Runner(alias_or_name(alias, n))]);
} else if let Some(n) = clause.resolver().as_ref() {
return Ok(vec![CapabilityId::Resolver(alias_or_name(alias, n))]);
} else if let Some(OneOrMany::One(n)) = clause.event().as_ref() {
return Ok(vec![CapabilityId::Event(alias_or_name(alias, n))]);
} else if let Some(OneOrMany::Many(events)) = clause.event().as_ref() {
return 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\" field can only be specified when one `event` is supplied",
)),
(None, Some(_), _) => Err(Error::validate(
"\"filter\" field can only be specified when one `event` is supplied",
)),
(Some(_), Some(_), _) => Err(Error::validate(
"\"as\",\"filter\" fields 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(_) = clause.event_stream().as_ref() {
return Ok(vec![CapabilityId::EventStream(alias_or_path(
path,
&DEFAULT_EVENT_STREAM_PATH,
))]);
}
// Unknown 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,
)))
}
}
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) => p.as_str(),
CapabilityId::EventStream(p) => p.as_str(),
CapabilityId::Protocol(p) | CapabilityId::Directory(p) => p.as_str(),
};
write!(f, "{}", s)
}
}
/// A list of offer targets.
#[derive(CheckedVec, Debug)]
#[checked_vec(
expected = "a nonempty array of offer targets, with unique elements",
min_length = 1,
unique_items = true
)]
pub struct OfferTo(pub Vec<OfferToRef>);
/// A list of rights.
#[derive(CheckedVec, Debug)]
#[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<Path>`.
#[derive(OneOrMany, Debug, Clone)]
#[one_or_many(
expected = "a name or path or nonempty array of names or paths, with unique elements",
inner_type = "NameOrPath",
min_length = 1,
unique_items = true
)]
pub struct OneOrManyNameOrPaths;
/// 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<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;
/// The stop timeout configured in an environment.
#[derive(Debug, Clone, Copy)]
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))
}
}
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`, where `name` contains only
/// alphanumeric characters, `-`, `_`, and `.`.
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 this component. Parsed as `self`.
Self_,
}
/// 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::Self_ => write!(f, "self"),
}
}
}
/// A reference in a `use from`.
#[derive(Debug, PartialEq, Eq, Hash, Clone, Reference)]
#[reference(expected = "\"parent\", \"framework\", or none")]
pub enum UseFromRef {
/// A reference to the parent.
Parent,
/// A reference to the framework.
Framework,
}
/// 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\", 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_,
}
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 = "\"parent\", \"framework\", \"self\", \"#<child-name>\", or \"#<collection-name>\""
)]
pub enum OfferToRef {
/// A reference to a child or collection.
Named(Name),
}
/// 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_,
}
/// A right or bundle of rights to apply to a directory.
#[derive(Deserialize, Clone, Debug, Eq, PartialEq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum Right {
// Individual
Connect,
Enumerate,
Execute,
GetAttributes,
ModifyDirectory,
ReadBytes,
Traverse,
UpdateAttributes,
WriteBytes,
Admin,
// 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::Admin => "admin",
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 `fio2::Operations`.
pub fn expand(&self) -> Vec<fio2::Operations> {
match self {
Self::Connect => vec![fio2::Operations::Connect],
Self::Enumerate => vec![fio2::Operations::Enumerate],
Self::Execute => vec![fio2::Operations::Execute],
Self::GetAttributes => vec![fio2::Operations::GetAttributes],
Self::ModifyDirectory => vec![fio2::Operations::ModifyDirectory],
Self::ReadBytes => vec![fio2::Operations::ReadBytes],
Self::Traverse => vec![fio2::Operations::Traverse],
Self::UpdateAttributes => vec![fio2::Operations::UpdateAttributes],
Self::WriteBytes => vec![fio2::Operations::WriteBytes],
Self::Admin => vec![fio2::Operations::Admin],
Self::ReadAlias => vec![
fio2::Operations::Connect,
fio2::Operations::Enumerate,
fio2::Operations::Traverse,
fio2::Operations::ReadBytes,
fio2::Operations::GetAttributes,
],
Self::WriteAlias => vec![
fio2::Operations::Connect,
fio2::Operations::Enumerate,
fio2::Operations::Traverse,
fio2::Operations::WriteBytes,
fio2::Operations::ModifyDirectory,
fio2::Operations::UpdateAttributes,
],
Self::ExecuteAlias => vec![
fio2::Operations::Connect,
fio2::Operations::Enumerate,
fio2::Operations::Traverse,
fio2::Operations::Execute,
],
Self::ReadWriteAlias => vec![
fio2::Operations::Connect,
fio2::Operations::Enumerate,
fio2::Operations::Traverse,
fio2::Operations::ReadBytes,
fio2::Operations::WriteBytes,
fio2::Operations::ModifyDirectory,
fio2::Operations::GetAttributes,
fio2::Operations::UpdateAttributes,
],
Self::ReadExecuteAlias => vec![
fio2::Operations::Connect,
fio2::Operations::Enumerate,
fio2::Operations::Traverse,
fio2::Operations::ReadBytes,
fio2::Operations::GetAttributes,
fio2::Operations::Execute,
],
}
}
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct Document {
pub program: Option<Map<String, Value>>,
pub r#use: Option<Vec<Use>>,
pub expose: Option<Vec<Expose>>,
pub offer: Option<Vec<Offer>>,
pub capabilities: Option<Vec<Capability>>,
pub children: Option<Vec<Child>>,
pub collections: Option<Vec<Collection>>,
pub facets: Option<Map<String, Value>>,
pub environments: Option<Vec<Environment>>,
}
impl Document {
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()).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![])
}
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "lowercase")]
pub enum EnvironmentExtends {
Realm,
None,
}
/// An Environment defines properties which affect the behavior of components within a realm, such
/// as its resolver.
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct Environment {
/// This name is used to reference the environment assigned to the component's children
pub name: Name,
// Whether the environment state should extend its realm, or start with empty property set.
// When not set, its value is assumed to be EnvironmentExtends::None.
pub extends: Option<EnvironmentExtends>,
pub runners: Option<Vec<RunnerRegistration>>,
pub resolvers: Option<Vec<ResolverRegistration>>,
#[serde(rename(deserialize = "__stop_timeout_ms"))]
pub stop_timeout_ms: Option<StopTimeoutMs>,
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct RunnerRegistration {
pub runner: Name,
pub from: RegistrationRef,
pub r#as: Option<Name>,
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct ResolverRegistration {
pub resolver: Name,
pub from: RegistrationRef,
pub scheme: cm_types::UrlScheme,
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct Capability {
pub service: Option<Name>,
pub protocol: Option<OneOrMany<Name>>,
pub directory: Option<Name>,
pub storage: Option<Name>,
pub runner: Option<Name>,
pub resolver: Option<Name>,
pub from: Option<CapabilityFromRef>,
pub path: Option<Path>,
pub rights: Option<Rights>,
pub backing_dir: Option<Name>,
pub subdir: Option<RelativePath>,
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct Use {
pub service: Option<Name>,
pub protocol: Option<OneOrMany<Name>>,
pub directory: Option<Name>,
pub storage: Option<Name>,
pub runner: Option<Name>,
pub from: Option<UseFromRef>,
pub path: Option<Path>,
pub r#as: Option<Name>,
pub rights: Option<Rights>,
pub subdir: Option<RelativePath>,
pub event: Option<OneOrMany<Name>>,
pub event_stream: Option<OneOrMany<Name>>,
pub filter: Option<Map<String, Value>>,
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct Expose {
pub service: Option<Name>,
pub protocol: Option<OneOrMany<Name>>,
pub directory: Option<Name>,
pub runner: Option<Name>,
pub resolver: Option<Name>,
pub from: OneOrMany<ExposeFromRef>,
pub r#as: Option<Name>,
pub to: Option<ExposeToRef>,
pub rights: Option<Rights>,
pub subdir: Option<RelativePath>,
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct Offer {
pub service: Option<Name>,
pub protocol: Option<OneOrMany<Name>>,
pub directory: Option<Name>,
pub storage: Option<Name>,
pub runner: Option<Name>,
pub resolver: Option<Name>,
pub event: Option<OneOrMany<Name>>,
pub from: OneOrMany<OfferFromRef>,
pub to: OfferTo,
pub r#as: Option<Name>,
pub rights: Option<Rights>,
pub subdir: Option<RelativePath>,
pub dependency: Option<DependencyType>,
pub filter: Option<Map<String, Value>>,
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct Child {
pub name: Name,
pub url: Url,
#[serde(default)]
pub startup: StartupMode,
pub environment: Option<EnvironmentRef>,
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct Collection {
pub name: Name,
pub durability: Durability,
pub environment: Option<EnvironmentRef>,
}
pub trait FromClause {
fn from_(&self) -> OneOrMany<AnyRef<'_>>;
}
/// Type for generic functions that need to adjust their behavior based on the type of clause
/// provided.
// TODO: Refactor the methods that use this so they don't require this, probably by breaking
// them up.
#[derive(Debug, PartialEq, Eq)]
pub enum RoutingClauseType {
Capability,
Use,
Offer,
Expose,
}
pub trait CapabilityClause {
fn service(&self) -> &Option<Name>;
fn protocol(&self) -> Option<OneOrMany<Name>>;
fn directory(&self) -> Option<Name>;
fn storage(&self) -> &Option<Name>;
fn runner(&self) -> &Option<Name>;
fn resolver(&self) -> &Option<Name>;
fn event(&self) -> &Option<OneOrMany<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_name(&self) -> &'static str;
fn decl_type(&self) -> &'static str;
fn supported(&self) -> &[&'static str];
}
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 RightsClause {
fn rights(&self) -> Option<&Rights>;
}
impl CapabilityClause for Capability {
fn service(&self) -> &Option<Name> {
&self.service
}
fn protocol(&self) -> Option<OneOrMany<Name>> {
self.protocol.as_ref().map(|o| match o {
OneOrMany::One(n) => OneOrMany::One(n.clone()),
OneOrMany::Many(v) => OneOrMany::Many(v.iter().map(|n| n.clone()).collect()),
})
}
fn directory(&self) -> Option<Name> {
self.directory.as_ref().map(|n| n.clone())
}
fn storage(&self) -> &Option<Name> {
&self.storage
}
fn runner(&self) -> &Option<Name> {
&self.runner
}
fn resolver(&self) -> &Option<Name> {
&self.resolver
}
fn event(&self) -> &Option<OneOrMany<Name>> {
&None
}
fn event_stream(&self) -> &Option<OneOrMany<Name>> {
&None
}
fn capability_name(&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 {
panic!("Missing capability name")
}
}
fn decl_type(&self) -> &'static str {
"capability"
}
fn supported(&self) -> &[&'static str] {
&["service", "protocol", "directory", "storage", "runner", "resolver"]
}
}
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 Use {
fn service(&self) -> &Option<Name> {
&self.service
}
fn protocol(&self) -> Option<OneOrMany<Name>> {
self.protocol.clone()
}
fn directory(&self) -> Option<Name> {
self.directory.clone()
}
fn storage(&self) -> &Option<Name> {
&self.storage
}
fn runner(&self) -> &Option<Name> {
&self.runner
}
fn resolver(&self) -> &Option<Name> {
&None
}
fn event(&self) -> &Option<OneOrMany<Name>> {
&self.event
}
fn event_stream(&self) -> &Option<OneOrMany<Name>> {
&self.event_stream
}
fn capability_name(&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.event.is_some() {
"event"
} 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"]
}
}
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 CapabilityClause for Expose {
fn service(&self) -> &Option<Name> {
&self.service
}
// TODO(fxbug.dev/340156): Only OneOrMany::One protocol is supported for now. Teach `expose` rules to accept
// `Many` protocols.
fn protocol(&self) -> Option<OneOrMany<Name>> {
self.protocol.clone()
}
fn directory(&self) -> Option<Name> {
self.directory.clone()
}
fn storage(&self) -> &Option<Name> {
&None
}
fn runner(&self) -> &Option<Name> {
&self.runner
}
fn resolver(&self) -> &Option<Name> {
&self.resolver
}
fn event(&self) -> &Option<OneOrMany<Name>> {
&None
}
fn event_stream(&self) -> &Option<OneOrMany<Name>> {
&None
}
fn capability_name(&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 {
panic!("Missing capability name")
}
}
fn decl_type(&self) -> &'static str {
"expose"
}
fn supported(&self) -> &[&'static str] {
&["service", "protocol", "directory", "runner", "resolver"]
}
}
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<Name> {
&self.service
}
fn protocol(&self) -> Option<OneOrMany<Name>> {
self.protocol.clone()
}
fn directory(&self) -> Option<Name> {
self.directory.clone()
}
fn storage(&self) -> &Option<Name> {
&self.storage
}
fn runner(&self) -> &Option<Name> {
&self.runner
}
fn resolver(&self) -> &Option<Name> {
&self.resolver
}
fn event(&self) -> &Option<OneOrMany<Name>> {
&self.event
}
fn event_stream(&self) -> &Option<OneOrMany<Name>> {
&None
}
fn capability_name(&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 {
panic!("Missing capability name")
}
}
fn decl_type(&self) -> &'static str {
"offer"
}
fn supported(&self) -> &[&'static str] {
&["service", "protocol", "directory", "storage", "runner", "resolver", "event"]
}
}
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 alias_or_name_or_path(alias: Option<&NameOrPath>, id: &NameOrPath) -> NameOrPath {
alias.map(|a| a).unwrap_or(id).clone()
}
#[cfg(test)]
mod tests {
use super::*;
use cm_json::{self, Error as JsonError};
use error::Error;
use matches::assert_matches;
use serde_json;
use serde_json5;
use std::path::Path;
// 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!("#-".parse::<OfferFromRef>(), Ok(OfferFromRef::Named(name)) if name == "-");
assert_matches!("#_".parse::<OfferFromRef>(), Ok(OfferFromRef::Named(name)) if name == "_");
assert_matches!("#7".parse::<OfferFromRef>(), Ok(OfferFromRef::Named(name)) if name == "7");
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),
("admin", Right::Admin),
("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<fio2::Operations>) {
assert_eq!(input.expand(), expected);
}
test_expand_rights! {
(Right::Connect, vec![fio2::Operations::Connect]),
(Right::Enumerate, vec![fio2::Operations::Enumerate]),
(Right::Execute, vec![fio2::Operations::Execute]),
(Right::GetAttributes, vec![fio2::Operations::GetAttributes]),
(Right::ModifyDirectory, vec![fio2::Operations::ModifyDirectory]),
(Right::ReadBytes, vec![fio2::Operations::ReadBytes]),
(Right::Traverse, vec![fio2::Operations::Traverse]),
(Right::UpdateAttributes, vec![fio2::Operations::UpdateAttributes]),
(Right::WriteBytes, vec![fio2::Operations::WriteBytes]),
(Right::Admin, vec![fio2::Operations::Admin]),
(Right::ReadAlias, vec![
fio2::Operations::Connect,
fio2::Operations::Enumerate,
fio2::Operations::Traverse,
fio2::Operations::ReadBytes,
fio2::Operations::GetAttributes,
]),
(Right::WriteAlias, vec![
fio2::Operations::Connect,
fio2::Operations::Enumerate,
fio2::Operations::Traverse,
fio2::Operations::WriteBytes,
fio2::Operations::ModifyDirectory,
fio2::Operations::UpdateAttributes,
]),
(Right::ExecuteAlias, vec![
fio2::Operations::Connect,
fio2::Operations::Enumerate,
fio2::Operations::Traverse,
fio2::Operations::Execute,
]),
(Right::ReadWriteAlias, vec![
fio2::Operations::Connect,
fio2::Operations::Enumerate,
fio2::Operations::Traverse,
fio2::Operations::ReadBytes,
fio2::Operations::WriteBytes,
fio2::Operations::ModifyDirectory,
fio2::Operations::GetAttributes,
fio2::Operations::UpdateAttributes,
]),
(Right::ReadExecuteAlias, vec![
fio2::Operations::Connect,
fio2::Operations::Enumerate,
fio2::Operations::Traverse,
fio2::Operations::ReadBytes,
fio2::Operations::GetAttributes,
fio2::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: OfferTo(vec![]),
r#as: None,
rights: None,
subdir: None,
dependency: None,
filter: None,
}
}
fn empty_use() -> Use {
Use {
service: None,
protocol: None,
directory: None,
storage: None,
runner: None,
from: None,
path: None,
r#as: None,
rights: None,
subdir: None,
event: None,
event_stream: None,
filter: None,
}
}
#[test]
fn test_capability_id() -> Result<(), Error> {
// service
assert_eq!(
CapabilityId::from_clause(
&Offer { service: Some("a".parse().unwrap()), ..empty_offer() },
RoutingClauseType::Offer
)?,
vec![CapabilityId::Service("a".parse().unwrap())]
);
assert_eq!(
CapabilityId::from_clause(
&Use { service: Some("a".parse().unwrap()), ..empty_use() },
RoutingClauseType::Use
)?,
vec![CapabilityId::UsedService("/svc/a".parse().unwrap())]
);
assert_eq!(
CapabilityId::from_clause(
&Use {
service: Some("a".parse().unwrap()),
path: Some("/b".parse().unwrap()),
..empty_use()
},
RoutingClauseType::Use
)?,
vec![CapabilityId::UsedService("/b".parse().unwrap())]
);
// protocol
assert_eq!(
CapabilityId::from_clause(
&Offer { protocol: Some(OneOrMany::One("a".parse().unwrap())), ..empty_offer() },
RoutingClauseType::Offer
)?,
vec![CapabilityId::Protocol("a".parse().unwrap())]
);
assert_eq!(
CapabilityId::from_clause(
&Offer {
protocol: Some(OneOrMany::Many(vec![
"a".parse().unwrap(),
"b".parse().unwrap()
],)),
..empty_offer()
},
RoutingClauseType::Offer
)?,
vec![
CapabilityId::Protocol("a".parse().unwrap()),
CapabilityId::Protocol("b".parse().unwrap())
]
);
assert_eq!(
CapabilityId::from_clause(
&Use { protocol: Some(OneOrMany::One("a".parse().unwrap())), ..empty_use() },
RoutingClauseType::Use
)?,
vec![CapabilityId::UsedProtocol("/svc/a".parse().unwrap())]
);
assert_eq!(
CapabilityId::from_clause(
&Use {
protocol: Some(OneOrMany::Many(vec![
"a".parse().unwrap(),
"b".parse().unwrap(),
],)),
..empty_use()
},
RoutingClauseType::Use
)?,
vec![
CapabilityId::UsedProtocol("/svc/a".parse().unwrap()),
CapabilityId::UsedProtocol("/svc/b".parse().unwrap())
]
);
assert_eq!(
CapabilityId::from_clause(
&Use {
protocol: Some(OneOrMany::One("a".parse().unwrap())),
path: Some("/b".parse().unwrap()),
..empty_use()
},
RoutingClauseType::Use
)?,
vec![CapabilityId::UsedProtocol("/b".parse().unwrap())]
);
// directory
assert_eq!(
CapabilityId::from_clause(
&Offer { directory: Some("a".parse().unwrap()), ..empty_offer() },
RoutingClauseType::Offer
)?,
vec![CapabilityId::Directory("a".parse().unwrap())]
);
assert_eq!(
CapabilityId::from_clause(
&Use {
directory: Some("a".parse().unwrap()),
path: Some("/b".parse().unwrap()),
..empty_use()
},
RoutingClauseType::Use
)?,
vec![CapabilityId::UsedDirectory("/b".parse().unwrap())]
);
// storage
assert_eq!(
CapabilityId::from_clause(
&Offer { storage: Some("cache".parse().unwrap()), ..empty_offer() },
RoutingClauseType::Offer
)?,
vec![CapabilityId::Storage("cache".parse().unwrap())],
);
// "as" aliasing.
assert_eq!(
CapabilityId::from_clause(
&Offer {
service: Some("a".parse().unwrap()),
r#as: Some("b".parse().unwrap()),
..empty_offer()
},
RoutingClauseType::Offer
)?,
vec![CapabilityId::Service("b".parse().unwrap())]
);
// Error case.
assert_matches!(
CapabilityId::from_clause(&empty_offer(), RoutingClauseType::Offer),
Err(_)
);
Ok(())
}
}