use crate::arena::Id;
use crate::id::NodeId;
use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Ref<T> {
Resolved(Id<T>),
Pending(PendingRef),
Broken(BreakCause),
}
impl<T> Ref<T> {
#[must_use]
pub const fn resolved(&self) -> Option<Id<T>> {
match self {
Self::Resolved(id) => Some(*id),
Self::Pending(_) | Self::Broken(_) => None,
}
}
#[must_use]
pub const fn is_resolved(&self) -> bool {
matches!(self, Self::Resolved(_))
}
#[must_use]
pub const fn is_pending(&self) -> bool {
matches!(self, Self::Pending(_))
}
#[must_use]
pub const fn is_broken(&self) -> bool {
matches!(self, Self::Broken(_))
}
#[must_use]
pub const fn pending_key(&self) -> Option<&PendingRef> {
match self {
Self::Pending(key) => Some(key),
Self::Resolved(_) | Self::Broken(_) => None,
}
}
#[must_use]
pub const fn break_cause(&self) -> Option<&BreakCause> {
match self {
Self::Broken(cause) => Some(cause),
Self::Resolved(_) | Self::Pending(_) => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum PendingRef {
ScopedName(ScopedName),
TypeIdentifier(TypeIdentifierKey),
RosTypeName(RosTypeName),
RepositoryId(RepositoryId),
XmlRef(XmlRefKey),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ScopedName {
absolute: bool,
segments: Vec<String>,
}
impl ScopedName {
#[must_use]
pub const fn absolute(segments: Vec<String>) -> Self {
Self {
absolute: true,
segments,
}
}
#[must_use]
pub const fn relative(segments: Vec<String>) -> Self {
Self {
absolute: false,
segments,
}
}
#[must_use]
pub const fn is_absolute(&self) -> bool {
self.absolute
}
#[must_use]
pub fn segments(&self) -> &[String] {
&self.segments
}
#[must_use]
pub fn last(&self) -> Option<&String> {
self.segments.last()
}
}
impl fmt::Display for ScopedName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.absolute {
write!(f, "::")?;
}
for (index, segment) in self.segments.iter().enumerate() {
if index > 0 {
write!(f, "::")?;
}
write!(f, "{segment}")?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum RosInterfaceKind {
Msg,
Srv,
Action,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct RosTypeName {
pub package: String,
pub kind: RosInterfaceKind,
pub name: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct RepositoryId(pub String);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct TypeIdentifierKey(pub [u8; 16]);
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct XmlRefKey(pub String);
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BreakCause {
NotFound {
what: PendingRef,
},
Ambiguous {
what: PendingRef,
candidates: Vec<NodeId>,
},
KindMismatch {
expected: &'static str,
found: &'static str,
},
IllegalCycle {
through: NodeId,
},
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
use super::*;
use crate::arena::Arena;
use alloc::string::ToString;
use alloc::vec;
#[test]
fn resolved_yields_its_handle_and_the_others_do_not() {
let mut arena: Arena<u32> = Arena::new();
let id = arena.insert(1).expect("insert");
let resolved: Ref<u32> = Ref::Resolved(id);
let pending: Ref<u32> = Ref::Pending(PendingRef::ScopedName(ScopedName::absolute(vec![
"Robot".to_string(),
"Pose".to_string(),
])));
let broken: Ref<u32> = Ref::Broken(BreakCause::NotFound {
what: PendingRef::RepositoryId(RepositoryId("IDL:Robot/Pose:1.0".to_string())),
});
assert_eq!(resolved.resolved(), Some(id));
assert_eq!(pending.resolved(), None);
assert_eq!(broken.resolved(), None);
assert!(resolved.is_resolved());
assert!(pending.is_pending());
assert!(broken.is_broken());
}
#[test]
fn pending_key_is_available_for_indexing_but_not_after_breaking() {
let key = PendingRef::RosTypeName(RosTypeName {
package: "geometry_msgs".to_string(),
kind: RosInterfaceKind::Msg,
name: "Pose".to_string(),
});
let pending: Ref<u32> = Ref::Pending(key.clone());
assert_eq!(pending.pending_key(), Some(&key));
let broken: Ref<u32> = Ref::Broken(BreakCause::NotFound { what: key });
assert_eq!(broken.pending_key(), None);
assert!(broken.break_cause().is_some());
}
#[test]
fn scoped_name_renders_idl_syntax() {
let name = ScopedName::absolute(vec!["Robot".to_string(), "Pose".to_string()]);
assert_eq!(name.to_string(), "::Robot::Pose");
let relative = ScopedName::relative(vec!["Pose".to_string()]);
assert_eq!(relative.to_string(), "Pose");
assert!(name.is_absolute());
assert!(!relative.is_absolute());
}
#[test]
fn scoped_names_order_segment_wise_for_deterministic_indices() {
let a = ScopedName::absolute(vec!["A".to_string()]);
let ab = ScopedName::absolute(vec!["A".to_string(), "B".to_string()]);
let b = ScopedName::absolute(vec!["B".to_string()]);
let mut all = vec![b.clone(), ab.clone(), a.clone()];
all.sort();
assert_eq!(all, vec![a, ab, b]);
}
}