blob: 7099dae79f7a9ef51764e8197da77e4972bad52c [file] [log] [blame]
// Copyright 2022 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 relative moniker describes the identity of a component instance in terms of its path
/// relative to another (unspecified) component in the component instance tree.
///
/// A self-reference moniker is a moniker with both empty "up" and "down" paths.
///
/// Relative monikers consist of two paths called "up" and "down".
/// - The "up" path describes a sequence of child-to-parent traversals heading towards the root of
/// the component instance tree.
/// - The "down" path describes a sequence of parent-to-child traversals heading towards a
/// different component instance in the tree.
///
/// These paths are minimal: no suffix segments of the "up" path can be a prefix segments of the
/// "down" path. All such common segments must be elided as part of canonicalizing the relative
/// moniker prior to construction.
///
/// Naming child monikers along both the "upwards" and "downwards" paths provides a strong
/// guarantee that relative monikers are only meaningful when interpreted within isomorphic
/// component instance subtrees. (Compare with relative filesystem path notations which use ".."
/// to perform upwards traversal and offer correspondingly weaker guarantees.)
///
/// For example, if two sibling component instances named "A" and "B" both possess relative
/// monikers for another component instance named "C", then A's moniker for C and B's moniker
/// for C will be distinct.
///
/// Display notation: ".", "./down1", ".\up1/down1", ".\up1\up2/down1", ...
use {
crate::instanced_child_moniker::InstancedChildMoniker,
moniker::{ChildMoniker, ChildMonikerBase, MonikerError, RelativeMoniker, RelativeMonikerBase},
std::{convert::TryFrom, fmt},
};
#[derive(Debug, Eq, PartialEq, Clone, Hash, Default)]
pub struct InstancedRelativeMoniker {
up_path: Vec<InstancedChildMoniker>,
down_path: Vec<InstancedChildMoniker>,
}
impl InstancedRelativeMoniker {
/// Create and allocate a `RelativeMoniker`, without instance ids
/// from this instanced moniker
pub fn without_instance_ids(&self) -> RelativeMoniker {
let up_path: Vec<ChildMoniker> =
self.up_path().iter().map(|c| c.without_instance_id()).collect();
let down_path: Vec<ChildMoniker> =
self.down_path().iter().map(|c| c.without_instance_id()).collect();
RelativeMoniker::new(up_path, down_path)
}
/// Transforms an `InstancedRelativeMoniker` into a representation where all dynamic children
/// have `0` value instance ids.
pub fn with_zero_value_instance_ids(&self) -> InstancedRelativeMoniker {
let up_path = self
.up_path()
.iter()
.map(|c| {
InstancedChildMoniker::new(
c.name().to_string(),
c.collection().map(|coll| coll.to_string()),
0,
)
})
.collect();
let down_path = self
.down_path()
.iter()
.map(|c| {
InstancedChildMoniker::new(
c.name().to_string(),
c.collection().map(|coll| coll.to_string()),
0,
)
})
.collect();
InstancedRelativeMoniker::new(up_path, down_path)
}
}
impl RelativeMonikerBase for InstancedRelativeMoniker {
type Part = InstancedChildMoniker;
fn new(up_path: Vec<Self::Part>, down_path: Vec<Self::Part>) -> Self {
Self { up_path, down_path }
}
fn up_path(&self) -> &Vec<Self::Part> {
&self.up_path
}
fn up_path_mut(&mut self) -> &mut Vec<Self::Part> {
&mut self.up_path
}
fn down_path(&self) -> &Vec<Self::Part> {
&self.down_path
}
fn down_path_mut(&mut self) -> &mut Vec<Self::Part> {
&mut self.down_path
}
}
impl fmt::Display for InstancedRelativeMoniker {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, ".")?;
for segment in self.up_path() {
write!(f, "\\{}", segment)?
}
for segment in self.down_path() {
write!(f, "/{}", segment)?
}
Ok(())
}
}
impl TryFrom<&str> for InstancedRelativeMoniker {
type Error = MonikerError;
fn try_from(input: &str) -> Result<Self, MonikerError> {
Self::parse(input)
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::instanced_abs_moniker::InstancedAbsoluteMoniker,
moniker::{AbsoluteMonikerBase, ChildMonikerBase, MonikerError},
std::convert::TryInto,
};
#[test]
fn instanced_relative_monikers() {
let me = InstancedRelativeMoniker::new(vec![], vec![]);
assert_eq!(true, me.is_self());
assert_eq!(".", format!("{}", me));
let ancestor = InstancedRelativeMoniker::new(
vec![
InstancedChildMoniker::new("a".to_string(), None, 1),
InstancedChildMoniker::new("b".to_string(), None, 2),
],
vec![],
);
assert_eq!(false, ancestor.is_self());
assert_eq!(".\\a:1\\b:2", format!("{}", ancestor));
let descendant = InstancedRelativeMoniker::new(
vec![],
vec![
InstancedChildMoniker::new("a".to_string(), None, 1),
InstancedChildMoniker::new("b".to_string(), None, 2),
],
);
assert_eq!(false, descendant.is_self());
assert_eq!("./a:1/b:2", format!("{}", descendant));
let sibling = InstancedRelativeMoniker::new(
vec![InstancedChildMoniker::new("a".to_string(), None, 1)],
vec![InstancedChildMoniker::new("b".to_string(), None, 2)],
);
assert_eq!(false, sibling.is_self());
assert_eq!(".\\a:1/b:2", format!("{}", sibling));
let cousin = InstancedRelativeMoniker::new(
vec![
InstancedChildMoniker::new("a".to_string(), None, 1),
InstancedChildMoniker::new("a0".to_string(), None, 1),
],
vec![
InstancedChildMoniker::new("b0".to_string(), None, 2),
InstancedChildMoniker::new("b".to_string(), None, 2),
],
);
assert_eq!(false, cousin.is_self());
assert_eq!(".\\a:1\\a0:1/b0:2/b:2", format!("{}", cousin));
}
#[test]
fn instanced_relative_monikers_from_absolute() {
let me = InstancedRelativeMoniker::from_absolute::<InstancedAbsoluteMoniker>(
&vec![].into(),
&vec![].into(),
);
assert_eq!(true, me.is_self());
assert_eq!(".", format!("{}", me));
let me = InstancedRelativeMoniker::from_absolute::<InstancedAbsoluteMoniker>(
&vec!["a:1", "b:2", "c:3"].into(),
&vec!["a:1", "b:2", "c:3"].into(),
);
assert_eq!(true, me.is_self());
assert_eq!(".", format!("{}", me));
let ancestor = InstancedRelativeMoniker::from_absolute::<InstancedAbsoluteMoniker>(
&vec!["a:1", "b:2"].into(),
&vec![].into(),
);
assert_eq!(false, ancestor.is_self());
assert_eq!(".\\b:2\\a:1", format!("{}", ancestor));
let ancestor = InstancedRelativeMoniker::from_absolute::<InstancedAbsoluteMoniker>(
&vec!["a:1", "b:2", "c:3", "d:4"].into(),
&vec!["a:1", "b:2"].into(),
);
assert_eq!(false, ancestor.is_self());
assert_eq!(".\\d:4\\c:3", format!("{}", ancestor));
let descendant = InstancedRelativeMoniker::from_absolute::<InstancedAbsoluteMoniker>(
&vec![].into(),
&vec!["a:1", "b:2"].into(),
);
assert_eq!(false, descendant.is_self());
assert_eq!("./a:1/b:2", format!("{}", descendant));
let descendant = InstancedRelativeMoniker::from_absolute::<InstancedAbsoluteMoniker>(
&vec!["a:1", "b:2"].into(),
&vec!["a:1", "b:2", "c:3", "d:4"].into(),
);
assert_eq!(false, descendant.is_self());
assert_eq!("./c:3/d:4", format!("{}", descendant));
let sibling = InstancedRelativeMoniker::from_absolute::<InstancedAbsoluteMoniker>(
&vec!["a:1"].into(),
&vec!["b:2"].into(),
);
assert_eq!(false, sibling.is_self());
assert_eq!(".\\a:1/b:2", format!("{}", sibling));
let sibling = InstancedRelativeMoniker::from_absolute::<InstancedAbsoluteMoniker>(
&vec!["c:3", "a:1"].into(),
&vec!["c:3", "b:2"].into(),
);
assert_eq!(false, sibling.is_self());
assert_eq!(".\\a:1/b:2", format!("{}", sibling));
let cousin = InstancedRelativeMoniker::from_absolute::<InstancedAbsoluteMoniker>(
&vec!["a0:1", "a:1"].into(),
&vec!["b0:2", "b:2"].into(),
);
assert_eq!(false, cousin.is_self());
assert_eq!(".\\a:1\\a0:1/b0:2/b:2", format!("{}", cousin));
let cousin = InstancedRelativeMoniker::from_absolute::<InstancedAbsoluteMoniker>(
&vec!["c:3", "d:4", "a0:1", "a:1"].into(),
&vec!["c:3", "d:4", "b0:2", "b:2"].into(),
);
assert_eq!(false, cousin.is_self());
assert_eq!(".\\a:1\\a0:1/b0:2/b:2", format!("{}", cousin));
}
#[test]
fn instanced_absolute_moniker_from_relative_success() {
// This test assumes the following topology:
// a
// b c
// d
// ====
// Test cases where relative moniker has up path *and* down path
let ac = InstancedAbsoluteMoniker::parse_str("/a:0/c:0").unwrap();
let abd = InstancedAbsoluteMoniker::parse_str("/a:0/b:0/d:0").unwrap();
let c_to_d = InstancedRelativeMoniker::from_absolute(&ac, &abd);
assert_eq!(abd, InstancedAbsoluteMoniker::from_relative(&ac, &c_to_d).unwrap());
// Test the opposite direction
let d_to_c = InstancedRelativeMoniker::from_absolute(&abd, &ac);
assert_eq!(ac, InstancedAbsoluteMoniker::from_relative(&abd, &d_to_c).unwrap());
// ===
// Test case where relative moniker has only up path
let ab = InstancedAbsoluteMoniker::parse_str("/a:0/b:0").unwrap();
let d_to_b = InstancedRelativeMoniker::from_absolute(&abd, &ab);
assert_eq!(ab, InstancedAbsoluteMoniker::from_relative(&abd, &d_to_b).unwrap());
// ===
// Test case where relative moniker has only down path
let b_to_d = InstancedRelativeMoniker::from_absolute(&ab, &abd);
assert_eq!(abd, InstancedAbsoluteMoniker::from_relative(&ab, &b_to_d).unwrap());
}
#[test]
fn instanced_absolute_moniker_from_relative_failure() {
// This test assumes the following topology:
// a
// b c
// d
// InstancedAbsolute moniker does not point to the right path
let a = InstancedAbsoluteMoniker::parse_str("/a:0").unwrap();
let ab = InstancedAbsoluteMoniker::parse_str("/a:0/b:0").unwrap();
let ac = InstancedAbsoluteMoniker::parse_str("/a:0/c:0").unwrap();
let abd = InstancedAbsoluteMoniker::parse_str("/a:0/b:0/d:0").unwrap();
let d_to_c = InstancedRelativeMoniker::from_absolute(&abd, &ac);
// error: `d_to_c`'s up_path is longer than `a`'s path
assert!(d_to_c.up_path().len() > a.path().len());
assert!(matches!(
InstancedAbsoluteMoniker::from_relative(&a, &d_to_c),
Err(MonikerError::InvalidMoniker { rep: _ })
));
let b_to_a = InstancedRelativeMoniker::from_absolute(&ab, &a);
// error: `b_to_a`'s up_path is the same length as `a`'s path, but up_path doesn't overlap with `a`'s path
assert!(b_to_a.up_path().len() == a.path().len());
assert!(matches!(
InstancedAbsoluteMoniker::from_relative(&a, &b_to_a),
Err(MonikerError::InvalidMoniker { rep: _ })
));
}
#[test]
fn instanced_relative_monikers_parse() {
for (up_path, down_path, string_to_parse) in vec![
(vec![], vec![], "."),
(vec!["a:0"], vec![], ".\\a:0"),
(vec!["a:0", "b:1"], vec![], ".\\a:0\\b:1"),
(vec!["a:0"], vec!["b:1"], ".\\a:0/b:1"),
(vec!["a:0", "b:1"], vec!["c:2"], ".\\a:0\\b:1/c:2"),
(vec!["a:0", "b:1"], vec!["c:2", "d:3"], ".\\a:0\\b:1/c:2/d:3"),
(vec!["a:0"], vec!["b:1", "c:2"], ".\\a:0/b:1/c:2"),
(vec![], vec!["a:0", "b:1"], "./a:0/b:1"),
(vec![], vec!["a:0"], "./a:0"),
] {
let up_path = up_path
.into_iter()
.map(|s| InstancedChildMoniker::parse(s).unwrap())
.collect::<Vec<InstancedChildMoniker>>();
let down_path = down_path
.into_iter()
.map(|s| InstancedChildMoniker::parse(s).unwrap())
.collect::<Vec<InstancedChildMoniker>>();
assert_eq!(
InstancedRelativeMoniker::new(up_path, down_path),
string_to_parse.try_into().unwrap()
);
}
for invalid_string_to_parse in vec![
".\\missing/instance/ids",
".\\only:0/one:1/is:2/missing/an:4/id:5",
".\\up:0/then-down:1\\then-up-again:2",
".\\\\double-leading-slash-up:0",
".//double-leading-slash-down:0",
"doesnt:0\\start:1\\with:2/a:3/dot:4",
"..//double:0/dot:0/oh:0/my:0",
".\\internal:1/../double:2/dot:3",
".\\internal:1/./single:2/dot:3",
"./negative-instance-id:-1",
] {
let res: Result<InstancedRelativeMoniker, MonikerError> =
invalid_string_to_parse.try_into();
assert!(
res.is_err(),
"didn't expect to correctly parse this: {:?}",
invalid_string_to_parse
);
}
}
}