1use crate::arena::Id;
6use crate::id::NodeId;
7use alloc::string::String;
8use alloc::vec::Vec;
9use core::fmt;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum Ref<T> {
18 Resolved(Id<T>),
20 Pending(PendingRef),
22 Broken(BreakCause),
24}
25
26impl<T> Ref<T> {
27 #[must_use]
29 pub const fn resolved(&self) -> Option<Id<T>> {
30 match self {
31 Self::Resolved(id) => Some(*id),
32 Self::Pending(_) | Self::Broken(_) => None,
33 }
34 }
35
36 #[must_use]
38 pub const fn is_resolved(&self) -> bool {
39 matches!(self, Self::Resolved(_))
40 }
41
42 #[must_use]
44 pub const fn is_pending(&self) -> bool {
45 matches!(self, Self::Pending(_))
46 }
47
48 #[must_use]
50 pub const fn is_broken(&self) -> bool {
51 matches!(self, Self::Broken(_))
52 }
53
54 #[must_use]
61 pub const fn pending_key(&self) -> Option<&PendingRef> {
62 match self {
63 Self::Pending(key) => Some(key),
64 Self::Resolved(_) | Self::Broken(_) => None,
65 }
66 }
67
68 #[must_use]
70 pub const fn break_cause(&self) -> Option<&BreakCause> {
71 match self {
72 Self::Broken(cause) => Some(cause),
73 Self::Resolved(_) | Self::Pending(_) => None,
74 }
75 }
76}
77
78#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
81pub enum PendingRef {
82 ScopedName(ScopedName),
84 TypeIdentifier(TypeIdentifierKey),
86 RosTypeName(RosTypeName),
88 RepositoryId(RepositoryId),
90 XmlRef(XmlRefKey),
92}
93
94#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
99pub struct ScopedName {
100 absolute: bool,
101 segments: Vec<String>,
102}
103
104impl ScopedName {
105 #[must_use]
107 pub const fn absolute(segments: Vec<String>) -> Self {
108 Self {
109 absolute: true,
110 segments,
111 }
112 }
113
114 #[must_use]
116 pub const fn relative(segments: Vec<String>) -> Self {
117 Self {
118 absolute: false,
119 segments,
120 }
121 }
122
123 #[must_use]
125 pub const fn is_absolute(&self) -> bool {
126 self.absolute
127 }
128
129 #[must_use]
131 pub fn segments(&self) -> &[String] {
132 &self.segments
133 }
134
135 #[must_use]
137 pub fn last(&self) -> Option<&String> {
138 self.segments.last()
139 }
140}
141
142impl fmt::Display for ScopedName {
143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144 if self.absolute {
145 write!(f, "::")?;
146 }
147 for (index, segment) in self.segments.iter().enumerate() {
148 if index > 0 {
149 write!(f, "::")?;
150 }
151 write!(f, "{segment}")?;
152 }
153 Ok(())
154 }
155}
156
157#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
159pub enum RosInterfaceKind {
160 Msg,
162 Srv,
164 Action,
166}
167
168#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
170pub struct RosTypeName {
171 pub package: String,
173 pub kind: RosInterfaceKind,
175 pub name: String,
177}
178
179#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
181pub struct RepositoryId(pub String);
182
183#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
188pub struct TypeIdentifierKey(pub [u8; 16]);
189
190#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
192pub struct XmlRefKey(pub String);
193
194#[derive(Debug, Clone, PartialEq, Eq)]
199pub enum BreakCause {
200 NotFound {
202 what: PendingRef,
204 },
205 Ambiguous {
207 what: PendingRef,
209 candidates: Vec<NodeId>,
211 },
212 KindMismatch {
214 expected: &'static str,
216 found: &'static str,
218 },
219 IllegalCycle {
222 through: NodeId,
224 },
225}
226
227#[cfg(test)]
228mod tests {
229 #![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
231 use super::*;
232 use crate::arena::Arena;
233 use alloc::string::ToString;
234 use alloc::vec;
235
236 #[test]
237 fn resolved_yields_its_handle_and_the_others_do_not() {
238 let mut arena: Arena<u32> = Arena::new();
239 let id = arena.insert(1).expect("insert");
240
241 let resolved: Ref<u32> = Ref::Resolved(id);
242 let pending: Ref<u32> = Ref::Pending(PendingRef::ScopedName(ScopedName::absolute(vec![
243 "Robot".to_string(),
244 "Pose".to_string(),
245 ])));
246 let broken: Ref<u32> = Ref::Broken(BreakCause::NotFound {
247 what: PendingRef::RepositoryId(RepositoryId("IDL:Robot/Pose:1.0".to_string())),
248 });
249
250 assert_eq!(resolved.resolved(), Some(id));
251 assert_eq!(pending.resolved(), None);
252 assert_eq!(broken.resolved(), None);
253 assert!(resolved.is_resolved());
254 assert!(pending.is_pending());
255 assert!(broken.is_broken());
256 }
257
258 #[test]
259 fn pending_key_is_available_for_indexing_but_not_after_breaking() {
260 let key = PendingRef::RosTypeName(RosTypeName {
261 package: "geometry_msgs".to_string(),
262 kind: RosInterfaceKind::Msg,
263 name: "Pose".to_string(),
264 });
265 let pending: Ref<u32> = Ref::Pending(key.clone());
266 assert_eq!(pending.pending_key(), Some(&key));
267
268 let broken: Ref<u32> = Ref::Broken(BreakCause::NotFound { what: key });
269 assert_eq!(broken.pending_key(), None);
270 assert!(broken.break_cause().is_some());
271 }
272
273 #[test]
274 fn scoped_name_renders_idl_syntax() {
275 let name = ScopedName::absolute(vec!["Robot".to_string(), "Pose".to_string()]);
276 assert_eq!(name.to_string(), "::Robot::Pose");
277 let relative = ScopedName::relative(vec!["Pose".to_string()]);
278 assert_eq!(relative.to_string(), "Pose");
279 assert!(name.is_absolute());
280 assert!(!relative.is_absolute());
281 }
282
283 #[test]
284 fn scoped_names_order_segment_wise_for_deterministic_indices() {
285 let a = ScopedName::absolute(vec!["A".to_string()]);
286 let ab = ScopedName::absolute(vec!["A".to_string(), "B".to_string()]);
287 let b = ScopedName::absolute(vec!["B".to_string()]);
288 let mut all = vec![b.clone(), ab.clone(), a.clone()];
289 all.sort();
290 assert_eq!(all, vec![a, ab, b]);
291 }
292}