blob: fd981185bdcdc34ef5ea0e2e3716731aeb752fad [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
cm_rust::{ChildDecl, ComponentDecl},
fidl_fuchsia_sys2::StartupMode,
moniker::PartialMoniker,
std::{boxed::Box, cell::RefCell, collections::HashMap, convert::Into, sync::Arc, sync::Weak},
thiserror::Error,
};
/// Errors that may occur while building or operating on a `ComponentTree`.
#[derive(Error, Debug)]
pub enum ComponentTreeError {
#[error("no component declaration found for url `{0}`")]
ComponentDeclNotFound(String),
#[error("invalid child declaration in component declaration with url `{0}`")]
InvalidChildDecl(String),
#[error("no child node found with moniker `{0}`")]
ComponentNodeNotFound(PartialMoniker),
}
pub struct ComponentNode {
pub decl: Box<ComponentDecl>,
parent: Weak<ComponentNode>,
children: RefCell<HashMap<PartialMoniker, Arc<ComponentNode>>>,
}
pub struct ComponentTree {
pub root: Arc<ComponentNode>,
}
pub struct ComponentTreeBuilder {
decls_by_url: HashMap<String, ComponentDecl>,
}
impl ComponentTreeBuilder {
/// Constructs a `ComponentTreeBuilder` from a map of component declarations keyed by
/// component url.
pub fn new(decls_by_url: HashMap<String, ComponentDecl>) -> Self {
ComponentTreeBuilder { decls_by_url: decls_by_url }
}
fn new_root_node(decl: Box<ComponentDecl>) -> Arc<ComponentNode> {
Arc::new(ComponentNode {
decl: decl,
parent: Weak::new(),
children: RefCell::new(HashMap::new()),
})
}
fn new_child_node(decl: Box<ComponentDecl>, parent: &Arc<ComponentNode>) -> Arc<ComponentNode> {
Arc::new(ComponentNode {
decl: decl,
parent: Arc::downgrade(parent),
children: RefCell::new(HashMap::new()),
})
}
/// Constructs and returns a `ComponentTree` based at the component with url `root_url`,
/// consuming the builder. Returns an error if `root_url` or the url of any subsequent
/// child is not present in the builder's `decls_by_url` map, or if any `ComponentDecl`
/// in `decls_by_url` contains an invalid `ChildDecl`.
pub fn build<T: Into<String>>(
mut self,
root_url: T,
) -> Result<ComponentTree, ComponentTreeError> {
let root_url = root_url.into();
match self.decls_by_url.remove(&root_url) {
Some(root_decl) => {
let root_node = ComponentTreeBuilder::new_root_node(Box::new(root_decl));
if let Err(err) = self.add_descendants(&root_node) {
return Err(err);
}
Ok(ComponentTree { root: root_node })
}
None => Err(ComponentTreeError::ComponentDeclNotFound(root_url)),
}
}
fn add_descendants(&mut self, node: &Arc<ComponentNode>) -> Result<(), ComponentTreeError> {
for child in node.decl.children.iter() {
if child.name.is_empty() {
return Err(ComponentTreeError::InvalidChildDecl(child.url.to_string()));
}
match self.decls_by_url.remove(&child.url) {
Some(child_decl) => {
let child_node =
ComponentTreeBuilder::new_child_node(Box::new(child_decl), node);
if let Err(err) = self.add_descendants(&child_node) {
return Err(err);
}
node.children
.borrow_mut()
.insert(PartialMoniker::new(child.name.clone(), None), child_node);
}
None => {
return Err(ComponentTreeError::ComponentDeclNotFound(child.url.to_string()))
}
};
}
Ok(())
}
}
impl ComponentNode {
/// Returns the child node corresponding to `moniker`, or an error if `moniker` does not
/// match any child of this `ComponentNode`.
pub fn get_child_node(
self: &Arc<Self>,
moniker: &PartialMoniker,
) -> Result<Arc<ComponentNode>, ComponentTreeError> {
match self.children.borrow().get(moniker) {
Some(child_node) => Ok(Arc::clone(child_node)),
None => Err(ComponentTreeError::ComponentNodeNotFound(moniker.clone())),
}
}
/// Returns the parent of this `ComponentNode`, or `None` if the node has no parent.
pub fn try_get_parent(self: &Arc<Self>) -> Option<Arc<ComponentNode>> {
self.parent.upgrade()
}
}
impl ComponentTree {
/// Given a sequence of `PartialMonikers`, attempts to follow the corresponding path
/// down the `ComponentTree` starting at the root node. The vector `monikers` corresponds
/// to a valid path for this ComponentTree if, for each i > 0, the i-th element is the
/// `PartialMoniker` of some child of a node whose `PartialMoniker` is the (i-1)-th element.
///
/// Returns the `ComponentNode` whose `PartialMoniker` is the final element of `monikers` if
/// the path is valid and nonempty, or returns the root node if the `monikers` is empty. Returns
/// an error if the path is invalid.
pub fn get_node(
&self,
monikers: &Vec<&PartialMoniker>,
) -> Result<Arc<ComponentNode>, ComponentTreeError> {
let mut node = Arc::clone(&self.root);
for moniker in monikers.iter() {
match node.get_child_node(&moniker) {
Ok(next_node) => {
node = Arc::clone(&next_node);
}
Err(err) => return Err(err),
};
}
Ok(node)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn new_child_decl(name: &String, url: &String) -> ChildDecl {
ChildDecl {
name: name.to_string(),
url: url.to_string(),
startup: StartupMode::Lazy,
environment: None,
}
}
fn new_component_decl(children: Vec<ChildDecl>) -> ComponentDecl {
ComponentDecl {
program: None,
uses: vec![],
exposes: vec![],
offers: vec![],
capabilities: vec![],
children: children,
collections: vec![],
facets: None,
environments: vec![],
}
}
fn new_root_node(decl: ComponentDecl) -> ComponentNode {
ComponentNode {
decl: Box::new(decl),
parent: Weak::new(),
children: RefCell::new(HashMap::new()),
}
}
fn new_child_node(decl: ComponentDecl, parent: &Arc<ComponentNode>) -> ComponentNode {
ComponentNode {
decl: Box::new(decl),
parent: Arc::downgrade(parent),
children: RefCell::new(HashMap::new()),
}
}
#[test]
// Checks that the `get_child_node` method successfully returns a child
// of a ComponentNode, and returns the expected error when a nonexistant
// child node is requested.
fn get_child_node() {
let root_decl = new_component_decl(vec![]);
let root_node = Arc::new(new_root_node(root_decl.clone()));
let foo_decl = new_component_decl(vec![]);
let foo_node = Arc::new(new_child_node(foo_decl.clone(), &root_node));
let foo_moniker = PartialMoniker::new("foo".to_string(), None);
root_node.children.borrow_mut().insert(foo_moniker.clone(), foo_node);
let valid_child = root_node.get_child_node(&foo_moniker);
assert!(valid_child.is_ok());
assert_eq!(*valid_child.unwrap().decl, foo_decl);
let other_moniker = PartialMoniker::new("other".to_string(), None);
let invalid_child = root_node.get_child_node(&other_moniker);
assert!(
invalid_child.is_err(),
ComponentTreeError::ComponentNodeNotFound(other_moniker.clone())
);
}
#[test]
// Checks that the `try_get_parent` method successfully returns the parent
// of a `ComponentNode`, and returns `None` when called on a `ComponentNode`
// without a parent.
fn try_get_parent_node() {
let root_decl = new_component_decl(vec![]);
let root_node = Arc::new(new_root_node(root_decl.clone()));
let foo_decl = new_component_decl(vec![]);
let foo_node = Arc::new(new_child_node(foo_decl.clone(), &root_node));
let has_parent = foo_node.try_get_parent();
assert!(has_parent.is_some());
assert_eq!(*has_parent.unwrap().decl, root_decl);
let no_parent = root_node.try_get_parent();
assert!(no_parent.is_none());
}
#[test]
// Builds a tree with a single node and checks that the node can be looked up at an
// empty sequence of PartialMonikers.
fn build_tree_and_look_up_single_node() {
let root_url = "root_url".to_string();
let root_decl = new_component_decl(vec![]);
let mut decls = HashMap::new();
decls.insert(root_url.clone(), root_decl.clone());
let builder = ComponentTreeBuilder::new(decls);
let build_tree_result = builder.build(root_url);
assert!(build_tree_result.is_ok());
let get_root_result = build_tree_result.unwrap().get_node(&vec![]);
assert!(get_root_result.is_ok());
let root_node = get_root_result.unwrap();
assert_eq!(*root_node.decl, root_decl);
assert!(root_node.try_get_parent().is_none());
assert!(root_node.children.borrow().is_empty());
}
#[test]
// Checks that the expected error is returned when the ComponentTreeBuilder's
// `build` method is called with an unrecognized component url.
fn build_tree_root_url_not_found() {
let root_url = "root_url".to_string();
let other_url = "other_url".to_string();
let mut decls = HashMap::new();
decls.insert(root_url.clone(), new_component_decl(vec![]));
let builder = ComponentTreeBuilder::new(decls);
let result = builder.build(other_url.clone());
assert!(result.is_err(), ComponentTreeError::ComponentDeclNotFound(other_url));
}
#[test]
// Builds a tree with 4 nodes:
//
// root
// / \
// foo bar
// /
// baz
//
// Checks that each node is populated correctly and can be looked up by a sequence
// of PartialMonikers. Also checks that lookup fails with an invalid sequence of
// PartialMonikers.
fn build_tree_and_look_up_multi_node() {
let root_url = "root_url".to_string();
let foo_url = "foo_url".to_string();
let bar_url = "bar_url".to_string();
let baz_url = "baz_url".to_string();
let foo_name = "foo".to_string();
let bar_name = "bar".to_string();
let baz_name = "baz".to_string();
let root_decl = new_component_decl(vec![
new_child_decl(&foo_name, &foo_url),
new_child_decl(&bar_name, &bar_url),
]);
let foo_decl = new_component_decl(vec![new_child_decl(&baz_name, &baz_url)]);
let bar_decl = new_component_decl(vec![]);
let baz_decl = new_component_decl(vec![]);
let mut decls = HashMap::new();
decls.insert(root_url.to_string(), root_decl.clone());
decls.insert(foo_url.to_string(), foo_decl.clone());
decls.insert(bar_url.to_string(), bar_decl.clone());
decls.insert(baz_url.to_string(), baz_decl.clone());
let builder = ComponentTreeBuilder::new(decls);
let build_tree_result = builder.build(root_url);
assert!(build_tree_result.is_ok());
let tree = build_tree_result.unwrap();
let foo_moniker = PartialMoniker::new(foo_name.clone(), None);
let bar_moniker = PartialMoniker::new(bar_name.clone(), None);
let baz_moniker = PartialMoniker::new(baz_name.clone(), None);
let get_root_result = tree.get_node(&vec![]);
assert!(get_root_result.is_ok());
let root_node = get_root_result.unwrap();
assert_eq!(*root_node.decl, root_decl);
assert_eq!(root_node.children.borrow().len(), 2);
assert!(root_node.try_get_parent().is_none());
let get_foo_result = tree.get_node(&vec![&foo_moniker]);
assert!(get_foo_result.is_ok());
let foo_node = get_foo_result.unwrap();
assert_eq!(*foo_node.decl, foo_decl);
assert_eq!(foo_node.children.borrow().len(), 1);
let foo_parent = foo_node.try_get_parent();
assert!(foo_parent.is_some());
assert_eq!(*foo_parent.unwrap().decl, root_decl);
let get_bar_result = tree.get_node(&vec![&bar_moniker]);
assert!(get_bar_result.is_ok());
let bar_node = get_bar_result.unwrap();
assert_eq!(*bar_node.decl, bar_decl);
assert!(bar_node.children.borrow().is_empty());
let bar_parent = bar_node.try_get_parent();
assert!(bar_parent.is_some());
assert_eq!(*bar_parent.unwrap().decl, root_decl);
let get_baz_result = tree.get_node(&vec![&foo_moniker, &baz_moniker]);
assert!(get_baz_result.is_ok());
let baz_node = get_baz_result.unwrap();
assert_eq!(*baz_node.decl, baz_decl);
assert!(baz_node.children.borrow().is_empty());
let baz_parent = baz_node.try_get_parent();
assert!(baz_parent.is_some());
assert_eq!(*baz_parent.unwrap().decl, foo_decl);
let other_moniker = PartialMoniker::new("other".to_string(), None);
assert!(
tree.get_node(&vec![&other_moniker]).is_err(),
ComponentTreeError::ComponentNodeNotFound(other_moniker.clone())
);
assert!(
tree.get_node(&vec![&foo_moniker, &other_moniker]).is_err(),
ComponentTreeError::ComponentNodeNotFound(other_moniker.clone())
);
}
}