blob: 8ccce3c794c96ec99ab6096b79683e9f4b9b0d78 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use cm_json::cm;
use lazy_static::lazy_static;
use regex::Regex;
use serde_derive::Deserialize;
use serde_json::{Map, Value};
use std::collections::HashMap;
use std::fmt;
pub const LAZY: &str = "lazy";
pub const EAGER: &str = "eager";
pub const PERSISTENT: &str = "persistent";
pub const TRANSIENT: &str = "transient";
/// Name of an object, such as a collection, component, or storage.
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct Name(String);
impl Name {
pub fn new(name: &str) -> Name {
Name(name.to_string())
}
pub fn as_str<'a>(&'a self) -> &'a str {
self.0.as_str()
}
pub fn to_string(&self) -> String {
self.0.to_string()
}
}
/// Format a `Ref` as a string.
impl fmt::Display for Name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0.as_str())
}
}
/// A relative reference to another object.
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum Ref {
Named(Name),
Realm,
Framework,
Self_,
}
/// Format a `Ref` as a string.
impl fmt::Display for Ref {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Ref::Named(name) => write!(f, "#{}", name.as_str()),
Ref::Realm => write!(f, "realm"),
Ref::Framework => write!(f, "framework"),
Ref::Self_ => write!(f, "self"),
}
}
}
/// Deserialize a string into a valid Ref.
impl<'de> serde::de::Deserialize<'de> for Ref {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
struct RefVisitor;
impl<'de> serde::de::Visitor<'de> for RefVisitor {
type Value = Ref;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a relative object reference")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
parse_reference(value)
.ok_or_else(|| E::custom(format!("invalid reference: \"{}\"", value)))
}
}
deserializer.deserialize_str(RefVisitor)
}
}
lazy_static! {
static ref NAME_RE: Regex = Regex::new(r"^#([A-Za-z0-9\-_.]+)$").unwrap();
}
/// Parse the given name of the form `#some-name`, returning the
/// name of the target if it is a valid target name, or `None`
/// otherwise.
pub fn parse_named_reference(reference: &str) -> Option<Name> {
NAME_RE.captures(reference).map_or(None, |c| c.get(1)).map(|c| Name::new(c.as_str()))
}
/// Parse the given relative reference, consisting of tokens such as
/// `realm` or `#child`. Returns None if the name could not be parsed.
pub fn parse_reference<'a>(value: &'a str) -> Option<Ref> {
if value.starts_with("#") {
return parse_named_reference(value).map(|c| Ref::Named(c));
}
match value {
"framework" => Some(Ref::Framework),
"realm" => Some(Ref::Realm),
"self" => Some(Ref::Self_),
_ => None,
}
}
/// Converts a valid cml right (including aliases) into a Vec<cm::Right> expansion. This function will
/// expand all valid right aliases and pass through non-aliased rights through to
/// Rights::map_token. None will be returned for invalid rights.
pub fn parse_right_token(token: &str) -> Option<Vec<cm::Right>> {
match token {
"r*" => Some(vec![
cm::Right::Connect,
cm::Right::Enumerate,
cm::Right::Traverse,
cm::Right::ReadBytes,
cm::Right::GetAttributes,
]),
"w*" => Some(vec![
cm::Right::Connect,
cm::Right::Enumerate,
cm::Right::Traverse,
cm::Right::WriteBytes,
cm::Right::ModifyDirectory,
cm::Right::UpdateAttributes,
]),
"x*" => Some(vec![
cm::Right::Connect,
cm::Right::Enumerate,
cm::Right::Traverse,
cm::Right::Execute,
]),
"rw*" => Some(vec![
cm::Right::Connect,
cm::Right::Enumerate,
cm::Right::Traverse,
cm::Right::ReadBytes,
cm::Right::WriteBytes,
cm::Right::ModifyDirectory,
cm::Right::GetAttributes,
cm::Right::UpdateAttributes,
]),
"rx*" => Some(vec![
cm::Right::Connect,
cm::Right::Enumerate,
cm::Right::Traverse,
cm::Right::ReadBytes,
cm::Right::GetAttributes,
cm::Right::Execute,
]),
_ => {
if let Some(right) = cm::Rights::map_token(token) {
return Some(vec![right]);
}
None
}
}
}
/// Converts a set of cml right tokens (including aliases) into a well formed cm::Rights expansion.
/// This function will expand all valid right aliases and pass through non-aliased rights through to
/// Rights::map_token. A cm::RightsValidationError will be returned on invalid rights.
pub fn parse_rights(right_tokens: &Vec<String>) -> Result<cm::Rights, cm::RightsValidationError> {
let mut rights = Vec::<cm::Right>::new();
for right_token in right_tokens.iter() {
match parse_right_token(right_token) {
Some(mut expanded_rights) => rights.append(&mut expanded_rights),
None => return Err(cm::RightsValidationError::UnknownRight),
}
}
cm::Rights::new(rights)
}
#[derive(Deserialize, Debug)]
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 children: Option<Vec<Child>>,
pub collections: Option<Vec<Collection>>,
pub storage: Option<Vec<Storage>>,
pub facets: Option<Map<String, Value>>,
pub runners: Option<Vec<Runner>>,
}
impl Document {
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(storage) = self.storage.as_ref() {
storage.iter().map(|s| &s.name).collect()
} else {
vec![]
}
}
pub fn all_storage_and_sources<'a>(&'a self) -> HashMap<&'a Name, &'a Ref> {
if let Some(storage) = self.storage.as_ref() {
storage.iter().map(|s| (&s.name, &s.from)).collect()
} else {
HashMap::new()
}
}
pub fn all_runner_names(&self) -> Vec<&Name> {
if let Some(runners) = self.runners.as_ref() {
runners.iter().map(|s| &s.name).collect()
} else {
vec![]
}
}
}
#[derive(Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum OneOrMany<T> {
One(T),
Many(Vec<T>),
}
impl<T: Clone> OneOrMany<T> {
pub fn to_vec(&self) -> Vec<T> {
match self {
OneOrMany::One(x) => return vec![x.clone()],
OneOrMany::Many(xs) => return xs.to_vec(),
}
}
}
#[derive(Deserialize, Debug)]
pub struct Use {
pub service: Option<String>,
pub service_protocol: Option<OneOrMany<String>>,
pub directory: Option<String>,
pub storage: Option<String>,
pub runner: Option<String>,
pub from: Option<Ref>,
pub r#as: Option<String>,
pub rights: Option<Vec<String>>,
}
#[derive(Deserialize, Debug)]
pub struct Expose {
pub service: Option<String>,
pub service_protocol: Option<OneOrMany<String>>,
pub directory: Option<String>,
pub runner: Option<String>,
pub from: Ref,
pub r#as: Option<String>,
pub to: Option<Ref>,
pub rights: Option<Vec<String>>,
}
#[derive(Deserialize, Debug)]
pub struct Offer {
pub service: Option<String>,
pub service_protocol: Option<OneOrMany<String>>,
pub directory: Option<String>,
pub storage: Option<String>,
pub runner: Option<String>,
pub from: Ref,
pub to: Vec<Ref>,
pub r#as: Option<String>,
pub rights: Option<Vec<String>>,
}
#[derive(Deserialize, Debug)]
pub struct Child {
pub name: Name,
pub url: String,
pub startup: Option<String>,
}
#[derive(Deserialize, Debug)]
pub struct Collection {
pub name: Name,
pub durability: String,
}
#[derive(Deserialize, Debug)]
pub struct Storage {
pub name: Name,
pub from: Ref,
pub path: String,
}
#[derive(Deserialize, Debug)]
pub struct Runner {
pub name: Name,
pub from: Ref,
pub path: String,
}
pub trait FromClause {
fn from(&self) -> &Ref;
}
pub trait CapabilityClause {
fn service(&self) -> &Option<String>;
fn service_protocol(&self) -> &Option<OneOrMany<String>>;
fn directory(&self) -> &Option<String>;
fn storage(&self) -> &Option<String>;
fn runner(&self) -> &Option<String>;
}
pub trait AsClause {
fn r#as(&self) -> Option<&String>;
}
impl CapabilityClause for Use {
fn service(&self) -> &Option<String> {
&self.service
}
fn service_protocol(&self) -> &Option<OneOrMany<String>> {
&self.service_protocol
}
fn directory(&self) -> &Option<String> {
&self.directory
}
fn storage(&self) -> &Option<String> {
&self.storage
}
fn runner(&self) -> &Option<String> {
&self.runner
}
}
impl AsClause for Use {
fn r#as(&self) -> Option<&String> {
self.r#as.as_ref()
}
}
impl FromClause for Expose {
fn from(&self) -> &Ref {
&self.from
}
}
impl CapabilityClause for Expose {
fn service(&self) -> &Option<String> {
&self.service
}
// TODO(340156): Only OneOrMany::One service_protocol is supported for now. Teach `expose` rules to accept
// `Many` service_protocols.
fn service_protocol(&self) -> &Option<OneOrMany<String>> {
&self.service_protocol
}
fn directory(&self) -> &Option<String> {
&self.directory
}
fn storage(&self) -> &Option<String> {
&None
}
fn runner(&self) -> &Option<String> {
&self.runner
}
}
impl AsClause for Expose {
fn r#as(&self) -> Option<&String> {
self.r#as.as_ref()
}
}
impl FromClause for Offer {
fn from(&self) -> &Ref {
&self.from
}
}
impl CapabilityClause for Offer {
fn service(&self) -> &Option<String> {
&self.service
}
fn service_protocol(&self) -> &Option<OneOrMany<String>> {
&self.service_protocol
}
fn directory(&self) -> &Option<String> {
&self.directory
}
fn storage(&self) -> &Option<String> {
&self.storage
}
fn runner(&self) -> &Option<String> {
&self.runner
}
}
impl AsClause for Offer {
fn r#as(&self) -> Option<&String> {
self.r#as.as_ref()
}
}
impl FromClause for Storage {
fn from(&self) -> &Ref {
&self.from
}
}
#[cfg(test)]
mod tests {
use super::*;
use cm_json::{self, Error};
use test_util::assert_matches;
#[test]
fn test_parse_named_reference() {
assert_eq!(parse_named_reference("#some-child"), Some(Name::new("some-child")));
assert_eq!(parse_named_reference("#-"), Some(Name::new("-")));
assert_eq!(parse_named_reference("#_"), Some(Name::new("_")));
assert_eq!(parse_named_reference("#7"), Some(Name::new("7")));
assert_eq!(parse_named_reference("#"), None);
assert_eq!(parse_named_reference("some-child"), None);
}
#[test]
fn test_parse_reference_test() {
assert_eq!(parse_reference("realm"), Some(Ref::Realm));
assert_eq!(parse_reference("framework"), Some(Ref::Framework));
assert_eq!(parse_reference("self"), Some(Ref::Self_));
assert_eq!(parse_reference("#child"), Some(Ref::Named(Name::new("child"))));
assert_eq!(parse_reference("invalid"), None);
assert_eq!(parse_reference("#invalid-child^"), None);
}
fn parse_as_ref(input: &str) -> Result<Ref, Error> {
serde_json::from_value::<Ref>(cm_json::from_json_str(input)?)
.map_err(|e| Error::parse(format!("{}", e)))
}
#[test]
fn test_deserialize_ref() -> Result<(), Error> {
assert_eq!(parse_as_ref("\"self\"")?, Ref::Self_);
assert_eq!(parse_as_ref("\"realm\"")?, Ref::Realm);
assert_eq!(parse_as_ref("\"#child\"")?, Ref::Named(Name::new("child")));
assert_matches!(parse_as_ref(r#""invalid""#), Err(_));
Ok(())
}
#[test]
fn test_parse_rights() -> Result<(), Error> {
assert_eq!(
parse_rights(&vec!["r*".to_owned()])?,
cm::Rights(vec![
cm::Right::Connect,
cm::Right::Enumerate,
cm::Right::Traverse,
cm::Right::ReadBytes,
cm::Right::GetAttributes,
])
);
assert_eq!(
parse_rights(&vec!["w*".to_owned()])?,
cm::Rights(vec![
cm::Right::Connect,
cm::Right::Enumerate,
cm::Right::Traverse,
cm::Right::WriteBytes,
cm::Right::ModifyDirectory,
cm::Right::UpdateAttributes,
])
);
assert_eq!(
parse_rights(&vec!["x*".to_owned()])?,
cm::Rights(vec![
cm::Right::Connect,
cm::Right::Enumerate,
cm::Right::Traverse,
cm::Right::Execute,
])
);
assert_eq!(
parse_rights(&vec!["rw*".to_owned()])?,
cm::Rights(vec![
cm::Right::Connect,
cm::Right::Enumerate,
cm::Right::Traverse,
cm::Right::ReadBytes,
cm::Right::WriteBytes,
cm::Right::ModifyDirectory,
cm::Right::GetAttributes,
cm::Right::UpdateAttributes,
])
);
assert_eq!(
parse_rights(&vec!["rx*".to_owned()])?,
cm::Rights(vec![
cm::Right::Connect,
cm::Right::Enumerate,
cm::Right::Traverse,
cm::Right::ReadBytes,
cm::Right::GetAttributes,
cm::Right::Execute,
])
);
assert_eq!(
parse_rights(&vec!["rw*".to_owned(), "execute".to_owned()])?,
cm::Rights(vec![
cm::Right::Connect,
cm::Right::Enumerate,
cm::Right::Traverse,
cm::Right::ReadBytes,
cm::Right::WriteBytes,
cm::Right::ModifyDirectory,
cm::Right::GetAttributes,
cm::Right::UpdateAttributes,
cm::Right::Execute,
])
);
assert_eq!(
parse_rights(&vec!["connect".to_owned()])?,
cm::Rights(vec![cm::Right::Connect])
);
assert_eq!(
parse_rights(&vec!["connect".to_owned(), "read_bytes".to_owned()])?,
cm::Rights(vec![cm::Right::Connect, cm::Right::ReadBytes])
);
assert_matches!(parse_rights(&vec![]), Err(cm::RightsValidationError::EmptyRight));
assert_matches!(
parse_rights(&vec!["connec".to_owned()]),
Err(cm::RightsValidationError::UnknownRight)
);
assert_matches!(
parse_rights(&vec!["connect".to_owned(), "connect".to_owned()]),
Err(cm::RightsValidationError::DuplicateRight)
);
assert_matches!(
parse_rights(&vec!["rw*".to_owned(), "connect".to_owned()]),
Err(cm::RightsValidationError::DuplicateRight)
);
Ok(())
}
}