blob: 9ba81a0ab3b2a8aeff5685877fd5a28c9cea6f88 [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.
use {
cm_types::{LongName, Name},
core::cmp::Ordering,
moniker::{ChildName, ChildNameBase, MonikerError},
std::fmt,
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// An instanced child moniker locally identifies a child component instance using the name assigned by
/// its parent and its collection (if present). It is a building block for more complex monikers.
///
/// Display notation: "[collection:]name:instance_id".
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[derive(Eq, PartialEq, Clone, Hash)]
pub struct InstancedChildName {
name: LongName,
collection: Option<Name>,
instance: IncarnationId,
}
pub type IncarnationId = u32;
impl ChildNameBase for InstancedChildName {
/// Parses an `ChildName` from a string.
///
/// Input strings should be of the format `(<collection>:)?<name>:<instance_id>`, e.g. `foo:42`
/// or `coll:foo:42`.
fn parse<T: AsRef<str>>(rep: T) -> Result<Self, MonikerError> {
let rep = rep.as_ref();
let parts: Vec<&str> = rep.split(":").collect();
// An instanced moniker is either just a name (static instance), or
// collection:name:instance_id.
let (coll, name, instance) = match parts.len() {
2 => {
let instance = parts[1]
.parse::<IncarnationId>()
.map_err(|_| MonikerError::invalid_moniker(rep))?;
(None, parts[0], instance)
}
3 => {
let instance = parts[2]
.parse::<IncarnationId>()
.map_err(|_| MonikerError::invalid_moniker(rep))?;
(Some(parts[0]), parts[1], instance)
}
_ => return Err(MonikerError::invalid_moniker(rep)),
};
Self::try_new(name, coll, instance)
}
fn name(&self) -> &str {
self.name.as_str()
}
fn collection(&self) -> Option<&Name> {
self.collection.as_ref()
}
}
impl InstancedChildName {
pub fn try_new<S>(
name: S,
collection: Option<S>,
instance: IncarnationId,
) -> Result<Self, MonikerError>
where
S: AsRef<str> + Into<String>,
{
let name = LongName::new(name)?;
let collection = match collection {
Some(coll) => {
let coll_name = Name::new(coll)?;
Some(coll_name)
}
None => None,
};
Ok(Self { name, collection, instance })
}
/// Returns a moniker for a static child.
///
/// The returned value will have no `collection`, and will have an `instance_id` of 0.
pub fn static_child(name: &str) -> Result<Self, MonikerError> {
Self::try_new(name, None, 0)
}
/// Converts this child moniker into an instanced moniker.
pub fn from_child_moniker(m: &ChildName, instance: IncarnationId) -> Self {
Self::try_new(m.name(), m.collection().map(|c| c.as_str()), instance)
.expect("child moniker is guaranteed to be valid")
}
/// Convert an InstancedChildName to an allocated ChildName
/// without an InstanceId
pub fn without_instance_id(&self) -> ChildName {
ChildName::try_new(self.name(), self.collection().map(|c| c.as_str()))
.expect("moniker is guaranteed to be valid")
}
pub fn instance(&self) -> IncarnationId {
self.instance
}
pub fn format(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(coll) = &self.collection {
write!(f, "{}:{}:{}", coll, self.name, self.instance)
} else {
write!(f, "{}:{}", self.name, self.instance)
}
}
}
impl TryFrom<&str> for InstancedChildName {
type Error = MonikerError;
fn try_from(rep: &str) -> Result<Self, MonikerError> {
InstancedChildName::parse(rep)
}
}
impl Ord for InstancedChildName {
fn cmp(&self, other: &Self) -> Ordering {
(&self.collection, &self.name, &self.instance).cmp(&(
&other.collection,
&other.name,
&other.instance,
))
}
}
impl PartialOrd for InstancedChildName {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl fmt::Display for InstancedChildName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.format(f)
}
}
impl fmt::Debug for InstancedChildName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.format(f)
}
}
#[cfg(test)]
mod tests {
use {
super::*,
cm_types::{MAX_LONG_NAME_LENGTH, MAX_NAME_LENGTH},
};
#[test]
fn instanced_child_monikers() {
let m = InstancedChildName::try_new("test", None, 42).unwrap();
assert_eq!("test", m.name());
assert_eq!(None, m.collection());
assert_eq!(42, m.instance());
assert_eq!("test:42", format!("{}", m));
assert_eq!(m, InstancedChildName::try_from("test:42").unwrap());
assert_eq!("test", m.without_instance_id().to_string());
assert_eq!(m, InstancedChildName::from_child_moniker(&"test".try_into().unwrap(), 42));
let m = InstancedChildName::try_new("test", Some("coll"), 42).unwrap();
assert_eq!("test", m.name());
assert_eq!(Some(&Name::new("coll").unwrap()), m.collection());
assert_eq!(42, m.instance());
assert_eq!("coll:test:42", format!("{}", m));
assert_eq!(m, InstancedChildName::try_from("coll:test:42").unwrap());
assert_eq!("coll:test", m.without_instance_id().to_string());
assert_eq!(m, InstancedChildName::from_child_moniker(&"coll:test".try_into().unwrap(), 42));
let max_coll_length_part = "f".repeat(MAX_NAME_LENGTH);
let max_name_length_part: LongName = "f".repeat(MAX_LONG_NAME_LENGTH).parse().unwrap();
let m = InstancedChildName::parse(format!(
"{}:{}:42",
max_coll_length_part, max_name_length_part
))
.expect("valid moniker");
assert_eq!(max_name_length_part, m.name());
assert_eq!(Some(&Name::new(max_coll_length_part).unwrap()), m.collection());
assert_eq!(42, m.instance());
assert!(InstancedChildName::parse("").is_err(), "cannot be empty");
assert!(InstancedChildName::parse(":").is_err(), "cannot be empty with colon");
assert!(InstancedChildName::parse("::").is_err(), "cannot be empty with double colon");
assert!(InstancedChildName::parse("f:").is_err(), "second part cannot be empty with colon");
assert!(InstancedChildName::parse(":1").is_err(), "first part cannot be empty with colon");
assert!(
InstancedChildName::parse("f:f:").is_err(),
"third part cannot be empty with colon"
);
assert!(
InstancedChildName::parse("f::1").is_err(),
"second part cannot be empty with colon"
);
assert!(
InstancedChildName::parse(":f:1").is_err(),
"first part cannot be empty with colon"
);
assert!(
InstancedChildName::parse("f:f:1:1").is_err(),
"more than three colons not allowed"
);
assert!(InstancedChildName::parse("f:f").is_err(), "second part must be int");
assert!(InstancedChildName::parse("f:f:f").is_err(), "third part must be int");
assert!(InstancedChildName::parse("@:1").is_err(), "invalid character in name");
assert!(InstancedChildName::parse("@:f:1").is_err(), "invalid character in collection");
assert!(
InstancedChildName::parse("f:@:1").is_err(),
"invalid character in name with collection"
);
assert!(
InstancedChildName::parse(&format!("f:{}", "x".repeat(MAX_LONG_NAME_LENGTH + 1)))
.is_err(),
"name too long"
);
assert!(
InstancedChildName::parse(&format!("{}:x", "f".repeat(MAX_NAME_LENGTH + 1))).is_err(),
"collection too long"
);
}
#[test]
fn instanced_child_moniker_compare() {
let a = InstancedChildName::try_new("a", None, 1).unwrap();
let a2 = InstancedChildName::try_new("a", None, 2).unwrap();
let aa = InstancedChildName::try_new("a", Some("a"), 1).unwrap();
let aa2 = InstancedChildName::try_new("a", Some("a"), 2).unwrap();
let ab = InstancedChildName::try_new("a", Some("b"), 1).unwrap();
let ba = InstancedChildName::try_new("b", Some("a"), 1).unwrap();
let bb = InstancedChildName::try_new("b", Some("b"), 1).unwrap();
let aa_same = InstancedChildName::try_new("a", Some("a"), 1).unwrap();
assert_eq!(Ordering::Less, a.cmp(&a2));
assert_eq!(Ordering::Greater, a2.cmp(&a));
assert_eq!(Ordering::Less, a2.cmp(&aa));
assert_eq!(Ordering::Greater, aa.cmp(&a2));
assert_eq!(Ordering::Less, a.cmp(&ab));
assert_eq!(Ordering::Greater, ab.cmp(&a));
assert_eq!(Ordering::Less, a.cmp(&ba));
assert_eq!(Ordering::Greater, ba.cmp(&a));
assert_eq!(Ordering::Less, a.cmp(&bb));
assert_eq!(Ordering::Greater, bb.cmp(&a));
assert_eq!(Ordering::Less, aa.cmp(&aa2));
assert_eq!(Ordering::Greater, aa2.cmp(&aa));
assert_eq!(Ordering::Less, aa.cmp(&ab));
assert_eq!(Ordering::Greater, ab.cmp(&aa));
assert_eq!(Ordering::Less, aa.cmp(&ba));
assert_eq!(Ordering::Greater, ba.cmp(&aa));
assert_eq!(Ordering::Less, aa.cmp(&bb));
assert_eq!(Ordering::Greater, bb.cmp(&aa));
assert_eq!(Ordering::Equal, aa.cmp(&aa_same));
assert_eq!(Ordering::Equal, aa_same.cmp(&aa));
assert_eq!(Ordering::Greater, ab.cmp(&ba));
assert_eq!(Ordering::Less, ba.cmp(&ab));
assert_eq!(Ordering::Less, ab.cmp(&bb));
assert_eq!(Ordering::Greater, bb.cmp(&ab));
assert_eq!(Ordering::Less, ba.cmp(&bb));
assert_eq!(Ordering::Greater, bb.cmp(&ba));
}
}