blob: ba30d31f0e2a6e1d11b82731dd9183c5dadbf868 [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::{MAX_DYNAMIC_NAME_LENGTH, MAX_NAME_LENGTH},
core::cmp::{Ord, Ordering},
moniker::{validate_moniker_part, ChildMoniker, ChildMonikerBase, 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, Debug, Clone, Hash, Default)]
pub struct InstancedChildMoniker {
name: String,
collection: Option<String>,
instance: InstanceId,
rep: String,
}
pub type InstanceId = u32;
impl ChildMonikerBase for InstancedChildMoniker {
/// Parses an `ChildMoniker` 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<_> = rep.split(":").collect();
let invalid = || MonikerError::invalid_moniker(rep);
// An instanced moniker is either just a name (static instance), or
// collection:name:instance_id.
if parts.len() != 2 && parts.len() != 3 {
return Err(invalid());
}
for p in parts.iter() {
if p.is_empty() {
return Err(invalid());
}
}
let (name, coll, instance) = match parts.len() == 3 {
true => {
let name = parts[1].to_string();
let coll = parts[0].to_string();
let instance: InstanceId = match parts[2].parse() {
Ok(i) => i,
_ => {
return Err(invalid());
}
};
(name, Some(coll), instance)
}
false => {
let instance: InstanceId = match parts[1].parse() {
Ok(i) => i,
_ => {
return Err(invalid());
}
};
(parts[0].to_string(), None, instance)
}
};
validate_moniker_part(Some(&name), MAX_DYNAMIC_NAME_LENGTH)?;
validate_moniker_part(coll.as_deref(), MAX_NAME_LENGTH)?;
Ok(Self::new(name, coll, instance))
}
fn name(&self) -> &str {
&self.name
}
fn collection(&self) -> Option<&str> {
self.collection.as_ref().map(|s| &**s)
}
fn as_str(&self) -> &str {
&self.rep
}
}
impl InstancedChildMoniker {
// TODO(fxbug.dev/77563): This does not currently validate the String inputs.
pub fn new(name: String, collection: Option<String>, instance: InstanceId) -> Self {
assert!(!name.is_empty());
let rep = if let Some(c) = collection.as_ref() {
assert!(!c.is_empty());
format!("{}:{}:{}", c, name, instance)
} else {
format!("{}:{}", name, instance)
};
Self { name, collection, instance, rep }
}
/// Returns a moniker for a static child.
///
/// The returned value will have no `collection`, and will have an `instance_id` of 0.
///
/// TODO(fxbug.dev/77563): This does not currently validate the String inputs.
pub fn static_child(name: String) -> Self {
Self::new(name, None, 0)
}
/// Converts this child moniker into an instanced moniker.
pub fn from_child_moniker(m: &ChildMoniker, instance: InstanceId) -> Self {
Self::new(m.name.clone(), m.collection.clone(), instance)
}
/// Convert an InstancedChildMoniker to an allocated ChildMoniker
/// without an InstanceId
pub fn without_instance_id(&self) -> ChildMoniker {
ChildMoniker::new(self.name.clone(), self.collection.clone())
}
pub fn instance(&self) -> InstanceId {
self.instance
}
}
impl From<&str> for InstancedChildMoniker {
fn from(rep: &str) -> Self {
InstancedChildMoniker::parse(rep)
.expect(&format!("instanced moniker failed to parse: {}", rep))
}
}
impl Ord for InstancedChildMoniker {
fn cmp(&self, other: &Self) -> Ordering {
(&self.collection, &self.name, &self.instance).cmp(&(
&other.collection,
&other.name,
&other.instance,
))
}
}
impl PartialOrd for InstancedChildMoniker {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl fmt::Display for InstancedChildMoniker {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn instanced_child_monikers() {
let m = InstancedChildMoniker::new("test".to_string(), None, 42);
assert_eq!("test", m.name());
assert_eq!(None, m.collection());
assert_eq!(42, m.instance());
assert_eq!("test:42", m.as_str());
assert_eq!("test:42", format!("{}", m));
assert_eq!(m, InstancedChildMoniker::from("test:42"));
assert_eq!("test", m.without_instance_id().as_str());
assert_eq!(m, InstancedChildMoniker::from_child_moniker(&"test".into(), 42));
let m = InstancedChildMoniker::new("test".to_string(), Some("coll".to_string()), 42);
assert_eq!("test", m.name());
assert_eq!(Some("coll"), m.collection());
assert_eq!(42, m.instance());
assert_eq!("coll:test:42", m.as_str());
assert_eq!("coll:test:42", format!("{}", m));
assert_eq!(m, InstancedChildMoniker::from("coll:test:42"));
assert_eq!("coll:test", m.without_instance_id().as_str());
assert_eq!(m, InstancedChildMoniker::from_child_moniker(&"coll:test".into(), 42));
let max_coll_length_part = "f".repeat(MAX_NAME_LENGTH);
let max_name_length_part = "f".repeat(MAX_DYNAMIC_NAME_LENGTH);
let m = InstancedChildMoniker::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(max_coll_length_part.as_str()), m.collection());
assert_eq!(42, m.instance());
assert!(InstancedChildMoniker::parse("").is_err(), "cannot be empty");
assert!(InstancedChildMoniker::parse(":").is_err(), "cannot be empty with colon");
assert!(InstancedChildMoniker::parse("::").is_err(), "cannot be empty with double colon");
assert!(
InstancedChildMoniker::parse("f:").is_err(),
"second part cannot be empty with colon"
);
assert!(
InstancedChildMoniker::parse(":1").is_err(),
"first part cannot be empty with colon"
);
assert!(
InstancedChildMoniker::parse("f:f:").is_err(),
"third part cannot be empty with colon"
);
assert!(
InstancedChildMoniker::parse("f::1").is_err(),
"second part cannot be empty with colon"
);
assert!(
InstancedChildMoniker::parse(":f:1").is_err(),
"first part cannot be empty with colon"
);
assert!(
InstancedChildMoniker::parse("f:f:1:1").is_err(),
"more than three colons not allowed"
);
assert!(InstancedChildMoniker::parse("f:f").is_err(), "second part must be int");
assert!(InstancedChildMoniker::parse("f:f:f").is_err(), "third part must be int");
assert!(InstancedChildMoniker::parse("@:1").is_err(), "invalid character in name");
assert!(InstancedChildMoniker::parse("@:f:1").is_err(), "invalid character in collection");
assert!(
InstancedChildMoniker::parse("f:@:1").is_err(),
"invalid character in name with collection"
);
assert!(
InstancedChildMoniker::parse(&format!("f:{}", "x".repeat(MAX_DYNAMIC_NAME_LENGTH + 1)))
.is_err(),
"name too long"
);
assert!(
InstancedChildMoniker::parse(&format!("{}:x", "f".repeat(MAX_NAME_LENGTH + 1)))
.is_err(),
"collection too long"
);
}
#[test]
fn instanced_child_moniker_compare() {
let a = InstancedChildMoniker::new("a".to_string(), None, 1);
let a2 = InstancedChildMoniker::new("a".to_string(), None, 2);
let aa = InstancedChildMoniker::new("a".to_string(), Some("a".to_string()), 1);
let aa2 = InstancedChildMoniker::new("a".to_string(), Some("a".to_string()), 2);
let ab = InstancedChildMoniker::new("a".to_string(), Some("b".to_string()), 1);
let ba = InstancedChildMoniker::new("b".to_string(), Some("a".to_string()), 1);
let bb = InstancedChildMoniker::new("b".to_string(), Some("b".to_string()), 1);
let aa_same = InstancedChildMoniker::new("a".to_string(), Some("a".to_string()), 1);
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));
}
}