1use alloc::string::String;
26use alloc::vec::Vec;
27
28#[derive(Debug, Clone, PartialEq, Eq, Hash)]
32pub struct ProfileId(pub [u8; 16]);
33
34impl ProfileId {
35 #[must_use]
38 pub const fn nil() -> Self {
39 Self([0u8; 16])
40 }
41}
42
43impl Default for ProfileId {
44 fn default() -> Self {
45 Self::nil()
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
51pub enum PortDirection {
52 #[default]
54 In,
55 Out,
57 InOut,
59}
60
61#[derive(Debug, Clone, PartialEq, Eq, Default)]
64pub struct PortProfile {
65 pub id: ProfileId,
67 pub name: String,
69 pub data_type: String,
71 pub direction: PortDirection,
73 pub properties: Vec<(String, String)>,
75}
76
77#[derive(Debug, Clone, PartialEq, Eq, Default)]
79pub struct ConnectorProfile {
80 pub id: ProfileId,
82 pub name: String,
84 pub port_ids: Vec<ProfileId>,
86 pub properties: Vec<(String, String)>,
88}
89
90#[derive(Debug, Clone, PartialEq, Eq, Default)]
92pub struct ComponentProfile {
93 pub id: ProfileId,
95 pub type_name: String,
97 pub instance_name: String,
99 pub vendor: String,
101 pub version: String,
103 pub ports: Vec<PortProfile>,
105 pub connectors: Vec<ConnectorProfile>,
107 pub properties: Vec<(String, String)>,
109}
110
111pub trait Introspection {
117 fn get_component_profile(&self) -> &ComponentProfile;
119
120 fn get_port_profile(&self, id: &ProfileId) -> Option<&PortProfile> {
123 self.get_component_profile()
124 .ports
125 .iter()
126 .find(|p| &p.id == id)
127 }
128
129 fn get_connector_profile(&self, id: &ProfileId) -> Option<&ConnectorProfile> {
132 self.get_component_profile()
133 .connectors
134 .iter()
135 .find(|c| &c.id == id)
136 }
137
138 fn get_ports(&self) -> &[PortProfile] {
140 &self.get_component_profile().ports
141 }
142
143 fn get_connectors(&self) -> &[ConnectorProfile] {
145 &self.get_component_profile().connectors
146 }
147}
148
149#[cfg(test)]
150#[allow(clippy::expect_used)]
151mod tests {
152 use super::*;
153
154 fn pid(b: u8) -> ProfileId {
155 let mut a = [0u8; 16];
156 a[15] = b;
157 ProfileId(a)
158 }
159
160 fn sample_port(b: u8, name: &str, dir: PortDirection) -> PortProfile {
161 PortProfile {
162 id: pid(b),
163 name: name.into(),
164 data_type: "geometry::Pose".into(),
165 direction: dir,
166 properties: Vec::new(),
167 }
168 }
169
170 struct StubComponent {
171 profile: ComponentProfile,
172 }
173
174 impl Introspection for StubComponent {
175 fn get_component_profile(&self) -> &ComponentProfile {
176 &self.profile
177 }
178 }
179
180 fn build_stub() -> StubComponent {
181 let p1 = sample_port(1, "in_port", PortDirection::In);
182 let p2 = sample_port(2, "out_port", PortDirection::Out);
183 let conn = ConnectorProfile {
184 id: pid(10),
185 name: "loop".into(),
186 port_ids: alloc::vec![pid(1), pid(2)],
187 properties: Vec::new(),
188 };
189 let comp = ComponentProfile {
190 id: pid(99),
191 type_name: "robotics::Localizer".into(),
192 instance_name: "loc1".into(),
193 vendor: "ZeroDDS".into(),
194 version: "1.0".into(),
195 ports: alloc::vec![p1, p2],
196 connectors: alloc::vec![conn],
197 properties: Vec::new(),
198 };
199 StubComponent { profile: comp }
200 }
201
202 #[test]
203 fn get_component_profile_returns_component() {
204 let s = build_stub();
205 assert_eq!(s.get_component_profile().instance_name, "loc1");
206 }
207
208 #[test]
209 fn get_port_profile_returns_some_when_known() {
210 let s = build_stub();
211 let p = s.get_port_profile(&pid(1)).expect("port present");
212 assert_eq!(p.name, "in_port");
213 assert_eq!(p.direction, PortDirection::In);
214 }
215
216 #[test]
217 fn get_port_profile_returns_none_when_unknown() {
218 let s = build_stub();
219 assert!(s.get_port_profile(&pid(99)).is_none());
220 }
221
222 #[test]
223 fn get_connector_profile_returns_known_connector() {
224 let s = build_stub();
225 let c = s.get_connector_profile(&pid(10)).expect("connector");
226 assert_eq!(c.name, "loop");
227 assert_eq!(c.port_ids.len(), 2);
228 }
229
230 #[test]
231 fn get_ports_returns_all_two_ports() {
232 let s = build_stub();
233 assert_eq!(s.get_ports().len(), 2);
234 }
235
236 #[test]
237 fn get_connectors_returns_one_connector() {
238 let s = build_stub();
239 assert_eq!(s.get_connectors().len(), 1);
240 }
241
242 #[test]
243 fn nil_profile_id_has_zero_bytes() {
244 assert_eq!(ProfileId::nil().0, [0u8; 16]);
245 }
246
247 #[test]
248 fn default_port_direction_is_in() {
249 assert_eq!(PortDirection::default(), PortDirection::In);
250 }
251
252 #[test]
253 fn introspection_default_methods_compose_correctly() {
254 let s = build_stub();
257 assert_eq!(s.get_ports().len(), s.get_component_profile().ports.len());
258 }
259
260 #[test]
261 fn component_profile_field_round_trip() {
262 let cp = ComponentProfile {
263 id: pid(1),
264 type_name: "T".into(),
265 instance_name: "I".into(),
266 vendor: "V".into(),
267 version: "1.0".into(),
268 ports: Vec::new(),
269 connectors: Vec::new(),
270 properties: alloc::vec![("k".into(), "v".into())],
271 };
272 assert_eq!(
273 cp.properties[0],
274 (
275 alloc::string::String::from("k"),
276 alloc::string::String::from("v")
277 )
278 );
279 }
280}