blob: d1a391a7eb827e600efa620a52587e5fa573537d [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, HashSet},
fmt,
hash::Hash,
ops::Deref,
path,
},
};
pub use cm_types::{
DependencyType, Durability, Name, ParseError, Path, RelativePath, StartupMode, Url,
};
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),
EventStream(Name),
}
/// 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::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())),
_ => 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() {
return Ok(vec![CapabilityId::EventStream(name)]);
}
// 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(
OneOrMany::One(n),
None,
capability.capability_type(),
)?));
} else if let Some(n) = capability.storage() {
return Ok(Self::storages_from(Self::get_one_or_many_names(
OneOrMany::One(n),
None,
capability.capability_type(),
)?));
} else if let Some(n) = capability.runner() {
return Ok(Self::runners_from(Self::get_one_or_many_names(
OneOrMany::One(n),
None,
capability.capability_type(),
)?));
} else if let Some(n) = capability.resolver() {
return Ok(Self::resolvers_from(Self::get_one_or_many_names(
OneOrMany::One(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 Exposeclause, 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(
OneOrMany::One(n),
alias,
clause.capability_type(),
)?));
} else if let Some(n) = clause.storage() {
return Ok(Self::storages_from(Self::get_one_or_many_names(
OneOrMany::One(n),
alias,
clause.capability_type(),
)?));
} else if let Some(n) = clause.runner() {
return Ok(Self::runners_from(Self::get_one_or_many_names(
OneOrMany::One(n),
alias,
clause.capability_type(),
)?));
} else if let Some(n) = clause.resolver() {
return Ok(Self::resolvers_from(Self::get_one_or_many_names(
OneOrMany::One(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()),
},
};
}
// 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::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)]
#[checked_vec(
expected = "a nonempty array of rights, with unique elements",
min_length = 1,
unique_items = true
)]
pub struct Rights(pub Vec<Right>);
/// A list of event modes.
#[derive(CheckedVec, Debug, PartialEq)]
#[checked_vec(
expected = "a nonempty array of event modes, with unique elements",
min_length = 1,
unique_items = true
)]
pub struct EventModes(pub Vec<EventMode>);
/// 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;
/// The stop timeout configured in an environment.
#[derive(Debug, Clone, Copy, PartialEq)]
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`.
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_,
}
/// 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"),
}
}
}
/// A reference in a `use from`.
#[derive(Debug, PartialEq, Eq, Hash, Clone, Reference)]
#[reference(expected = "\"parent\", \"framework\", \"debug\", \"#<capability-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 capability declared on self.
///
/// This 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 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_,
}
#[derive(Deserialize, Clone, Debug, Eq, PartialEq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum EventMode {
/// Async events are allowed.
Async,
/// Sync events are allowed.
Sync,
}
#[derive(Deserialize, Debug, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct EventSubscription {
pub event: OneOrMany<Name>,
pub mode: Option<EventMode>,
}
/// 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 include: Option<Vec<String>>,
pub program: Option<Program>,
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>>,
}
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);
}
};
}
impl Document {
pub fn merge_from(
&mut self,
other: &mut Document,
include_path: &path::Path,
) -> Result<(), Error> {
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)?;
// Facets aren't an actively used feature so we don't need to support them. Also,
// the merge policy for facets would be non-trivial because they can contain nested maps.
if let Some(_) = other.facets {
return Err(Error::validate(format!(
"facets found in manifest include, which are not supported: {}",
include_path.display()
)));
}
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 my_program.runner.is_some() && other_program.runner.is_some() {
return Err(Error::validate(format!(
"manifest include had a conflicting `program.runner`: {}",
include_path.display()
)));
}
*(&mut my_program.runner) = other_program.runner.take();
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(())
}
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)]
#[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, PartialEq)]
#[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>>,
pub debug: Option<Vec<DebugRegistration>>,
#[serde(rename(deserialize = "__stop_timeout_ms"))]
pub stop_timeout_ms: Option<StopTimeoutMs>,
}
#[derive(Deserialize, Debug, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct RunnerRegistration {
pub runner: Name,
pub from: RegistrationRef,
pub r#as: Option<Name>,
}
#[derive(Deserialize, Debug, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct ResolverRegistration {
pub resolver: Name,
pub from: RegistrationRef,
pub scheme: cm_types::UrlScheme,
}
#[derive(Deserialize, Debug, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct Capability {
pub service: Option<OneOrMany<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, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct DebugRegistration {
pub protocol: Option<OneOrMany<Name>>,
pub from: OfferFromRef,
pub r#as: Option<Name>,
pub path: Option<Path>,
}
/// A list of event modes.
#[derive(CheckedVec, Debug, PartialEq)]
#[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)]
pub struct Program {
pub runner: Option<Name>,
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)
}
}
#[derive(Deserialize, Debug, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct Use {
pub service: Option<OneOrMany<Name>>,
pub protocol: Option<OneOrMany<Name>>,
pub directory: Option<Name>,
pub storage: 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<Name>,
pub filter: Option<Map<String, Value>>,
pub modes: Option<EventModes>,
pub subscriptions: Option<EventSubscriptions>,
}
#[derive(Deserialize, Debug, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct Expose {
pub service: Option<OneOrMany<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>,
pub modes: Option<EventModes>,
}
#[derive(Deserialize, Debug, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct Offer {
pub service: Option<OneOrMany<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: OneOrMany<OfferToRef>,
pub r#as: Option<Name>,
pub rights: Option<Rights>,
pub subdir: Option<RelativePath>,
pub dependency: Option<DependencyType>,
pub filter: Option<Map<String, Value>>,
pub modes: Option<EventModes>,
}
#[derive(Deserialize, Debug, PartialEq)]
#[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, PartialEq)]
#[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<'_>>;
}
pub trait CapabilityClause {
fn service(&self) -> Option<OneOrMany<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<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().map(|n| OneOrMany::One(n)),
self.storage().map(|n| OneOrMany::One(n)),
self.runner().map(|n| OneOrMany::One(n)),
self.resolver().map(|n| OneOrMany::One(n)),
self.event(),
self.event_stream().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 EventModesClause {
fn event_modes(&self) -> Option<&EventModes>;
}
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<Name> {
self.directory.clone()
}
fn storage(&self) -> Option<Name> {
self.storage.clone()
}
fn runner(&self) -> Option<Name> {
self.runner.clone()
}
fn resolver(&self) -> Option<Name> {
self.resolver.clone()
}
fn event(&self) -> Option<OneOrMany<Name>> {
None
}
fn event_stream(&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 {
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 DebugRegistration {
fn service(&self) -> Option<OneOrMany<Name>> {
None
}
fn protocol(&self) -> Option<OneOrMany<Name>> {
self.protocol.clone()
}
fn directory(&self) -> Option<Name> {
None
}
fn storage(&self) -> Option<Name> {
None
}
fn runner(&self) -> Option<Name> {
None
}
fn resolver(&self) -> Option<Name> {
None
}
fn event(&self) -> Option<OneOrMany<Name>> {
None
}
fn event_stream(&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> {
self.path.as_ref()
}
}
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<Name> {
self.directory.clone()
}
fn storage(&self) -> Option<Name> {
self.storage.clone()
}
fn runner(&self) -> Option<Name> {
None
}
fn resolver(&self) -> Option<Name> {
None
}
fn event(&self) -> Option<OneOrMany<Name>> {
self.event.clone()
}
fn event_stream(&self) -> Option<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.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 EventModesClause for Use {
fn event_modes(&self) -> Option<&EventModes> {
self.modes.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<Name> {
self.directory.clone()
}
fn storage(&self) -> Option<Name> {
None
}
fn runner(&self) -> Option<Name> {
self.runner.clone()
}
fn resolver(&self) -> Option<Name> {
self.resolver.clone()
}
fn event(&self) -> Option<OneOrMany<Name>> {
None
}
fn event_stream(&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.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 EventModesClause for Expose {
fn event_modes(&self) -> Option<&EventModes> {
self.modes.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<Name> {
self.directory.clone()
}
fn storage(&self) -> Option<Name> {
self.storage.clone()
}
fn runner(&self) -> Option<Name> {
self.runner.clone()
}
fn resolver(&self) -> Option<Name> {
self.resolver.clone()
}
fn event(&self) -> Option<OneOrMany<Name>> {
self.event.clone()
}
fn event_stream(&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 {
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 EventModesClause for Offer {
fn event_modes(&self) -> Option<&EventModes> {
self.modes.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()
}
#[cfg(test)]
mod tests {
use super::*;
use {
cm_json::{self, Error as JsonError},
error::Error,
matches::assert_matches,
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),
("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: OneOrMany::Many(vec![]),
r#as: None,
rights: None,
subdir: None,
dependency: None,
filter: None,
modes: None,
}
}
fn empty_use() -> Use {
Use {
service: None,
protocol: None,
directory: None,
storage: None,
from: None,
path: None,
r#as: None,
rights: None,
subdir: None,
event: None,
event_stream: None,
filter: None,
modes: None,
subscriptions: 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 {
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("a".parse().unwrap()),
..empty_offer()
},)?,
vec![CapabilityId::Directory("a".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("cache".parse().unwrap()),
..empty_offer()
},)?,
vec![CapabilityId::Storage("cache".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_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_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) {
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)
);
}
}