1use alloc::string::String;
19use alloc::vec::Vec;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23pub enum ReceptacleMultiplicity {
24 Simplex,
26 Multiplex,
28}
29
30#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct FacetDef {
33 pub name: String,
35 pub interface_id: String,
37}
38
39#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct ReceptacleDef {
42 pub name: String,
44 pub interface_id: String,
46 pub multiplicity: ReceptacleMultiplicity,
48}
49
50#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct EventSourceDef {
53 pub name: String,
55 pub event_type_id: String,
57 pub emit_only: bool,
60}
61
62#[derive(Debug, Clone, PartialEq, Eq)]
64pub struct EventSinkDef {
65 pub name: String,
67 pub event_type_id: String,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
73pub struct AttributeDef {
74 pub name: String,
76 pub type_spec: String,
78 pub readonly: bool,
80 pub set_raises: Vec<String>,
82 pub get_raises: Vec<String>,
84}
85
86#[derive(Debug, Clone, PartialEq, Eq, Default)]
88pub struct ComponentDef {
89 pub name: String,
91 pub repository_id: String,
93 pub base_component: Option<String>,
95 pub supported_interfaces: Vec<String>,
97 pub facets: Vec<FacetDef>,
99 pub receptacles: Vec<ReceptacleDef>,
101 pub event_sources: Vec<EventSourceDef>,
103 pub event_sinks: Vec<EventSinkDef>,
105 pub attributes: Vec<AttributeDef>,
107 pub primary_key: Vec<String>,
109}
110
111impl ComponentDef {
112 #[must_use]
114 pub fn is_keyed(&self) -> bool {
115 !self.primary_key.is_empty()
116 }
117
118 #[must_use]
120 pub fn port_count(&self) -> usize {
121 self.facets.len()
122 + self.receptacles.len()
123 + self.event_sources.len()
124 + self.event_sinks.len()
125 }
126
127 #[must_use]
136 pub fn is_equivalent_component_kind(&self, repo_id: &str) -> bool {
137 self.repository_id == repo_id
138 }
139
140 #[must_use]
144 pub fn get_component_def_repo_id(&self) -> &str {
145 &self.repository_id
146 }
147
148 #[must_use]
157 pub fn provide_facet(&self, name: &str) -> Option<&FacetDef> {
158 self.facets.iter().find(|f| f.name == name)
159 }
160
161 #[must_use]
164 pub fn get_all_facets(&self) -> &[FacetDef] {
165 &self.facets
166 }
167
168 #[must_use]
172 pub fn get_named_facets(&self, names: &[&str]) -> Vec<&FacetDef> {
173 names.iter().filter_map(|n| self.provide_facet(n)).collect()
174 }
175
176 #[must_use]
182 pub fn get_all_receptacles(&self) -> &[ReceptacleDef] {
183 &self.receptacles
184 }
185
186 #[must_use]
188 pub fn get_named_receptacles(&self, names: &[&str]) -> Vec<&ReceptacleDef> {
189 names
190 .iter()
191 .filter_map(|n| self.receptacles.iter().find(|r| r.name == *n))
192 .collect()
193 }
194
195 #[must_use]
202 pub fn get_all_publishers(&self) -> Vec<&EventSourceDef> {
203 self.event_sources.iter().filter(|s| !s.emit_only).collect()
204 }
205
206 #[must_use]
209 pub fn get_all_emitters(&self) -> Vec<&EventSourceDef> {
210 self.event_sources.iter().filter(|s| s.emit_only).collect()
211 }
212
213 #[must_use]
215 pub fn get_named_publishers(&self, names: &[&str]) -> Vec<&EventSourceDef> {
216 names
217 .iter()
218 .filter_map(|n| {
219 self.event_sources
220 .iter()
221 .find(|s| !s.emit_only && s.name == *n)
222 })
223 .collect()
224 }
225
226 #[must_use]
228 pub fn get_named_emitters(&self, names: &[&str]) -> Vec<&EventSourceDef> {
229 names
230 .iter()
231 .filter_map(|n| {
232 self.event_sources
233 .iter()
234 .find(|s| s.emit_only && s.name == *n)
235 })
236 .collect()
237 }
238
239 #[must_use]
248 pub fn supports_interface(&self, interface_repo_id: &str) -> bool {
249 self.supported_interfaces
250 .iter()
251 .any(|i| i == interface_repo_id)
252 }
253
254 #[must_use]
257 pub fn supported_interface_repo_ids(&self) -> &[String] {
258 &self.supported_interfaces
259 }
260}
261
262#[cfg(test)]
263#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
264mod tests {
265 use super::*;
266
267 fn trader() -> ComponentDef {
268 ComponentDef {
269 name: "Trader".into(),
270 repository_id: "IDL:demo/Trader:1.0".into(),
271 base_component: None,
272 supported_interfaces: alloc::vec![],
273 facets: alloc::vec![FacetDef {
274 name: "control".into(),
275 interface_id: "IDL:demo/Control:1.0".into(),
276 }],
277 receptacles: alloc::vec![ReceptacleDef {
278 name: "market_feed".into(),
279 interface_id: "IDL:demo/MarketFeed:1.0".into(),
280 multiplicity: ReceptacleMultiplicity::Simplex,
281 }],
282 event_sources: alloc::vec![EventSourceDef {
283 name: "trade_pub".into(),
284 event_type_id: "IDL:demo/Trade:1.0".into(),
285 emit_only: false,
286 }],
287 event_sinks: alloc::vec![EventSinkDef {
288 name: "alarm_sink".into(),
289 event_type_id: "IDL:demo/Alarm:1.0".into(),
290 }],
291 attributes: alloc::vec![AttributeDef {
292 name: "max_volume".into(),
293 type_spec: "long long".into(),
294 readonly: false,
295 set_raises: alloc::vec![],
296 get_raises: alloc::vec![],
297 }],
298 primary_key: alloc::vec![],
299 }
300 }
301
302 #[test]
303 fn trader_has_4_ports() {
304 let t = trader();
305 assert_eq!(t.port_count(), 4);
306 }
307
308 #[test]
309 fn unkeyed_component_is_not_keyed() {
310 assert!(!trader().is_keyed());
311 }
312
313 #[test]
314 fn keyed_component_detected() {
315 let mut t = trader();
316 t.primary_key.push("IDL:demo/TraderKey:1.0".into());
317 assert!(t.is_keyed());
318 }
319
320 #[test]
321 fn receptacle_multiplicity_distinct() {
322 assert_ne!(
323 ReceptacleMultiplicity::Simplex,
324 ReceptacleMultiplicity::Multiplex
325 );
326 }
327
328 #[test]
329 fn event_source_emit_only_default_false() {
330 let t = trader();
331 assert!(!t.event_sources[0].emit_only);
332 }
333
334 #[test]
335 fn attribute_readonly_skips_set_raises() {
336 let a = AttributeDef {
337 name: "version".into(),
338 type_spec: "string".into(),
339 readonly: true,
340 set_raises: alloc::vec![],
341 get_raises: alloc::vec!["IDL:demo/Bad:1.0".into()],
342 };
343 assert!(a.readonly);
344 assert!(a.set_raises.is_empty());
345 }
346
347 #[test]
352 fn is_equivalent_component_kind_matches_repo_id() {
353 let t = trader();
354 assert!(t.is_equivalent_component_kind("IDL:demo/Trader:1.0"));
355 assert!(!t.is_equivalent_component_kind("IDL:demo/Other:1.0"));
356 }
357
358 #[test]
359 fn get_component_def_repo_id_returns_repo_id() {
360 let t = trader();
361 assert_eq!(t.get_component_def_repo_id(), "IDL:demo/Trader:1.0");
362 }
363
364 #[test]
369 fn provide_facet_returns_facet_by_name() {
370 let t = trader();
371 let f = t.provide_facet("control").expect("control facet");
372 assert_eq!(f.name, "control");
373 }
374
375 #[test]
376 fn provide_facet_returns_none_for_unknown() {
377 let t = trader();
378 assert!(t.provide_facet("nonexistent").is_none());
379 }
380
381 #[test]
382 fn get_all_facets_returns_complete_list() {
383 let t = trader();
384 assert_eq!(t.get_all_facets().len(), 1);
385 }
386
387 #[test]
388 fn get_named_facets_filters_by_names() {
389 let t = trader();
390 let f = t.get_named_facets(&["control", "missing"]);
391 assert_eq!(f.len(), 1);
392 assert_eq!(f[0].name, "control");
393 }
394
395 #[test]
400 fn get_all_receptacles_returns_complete_list() {
401 let t = trader();
402 assert_eq!(t.get_all_receptacles().len(), 1);
403 assert_eq!(t.get_all_receptacles()[0].name, "market_feed");
404 }
405
406 #[test]
407 fn get_named_receptacles_filters() {
408 let t = trader();
409 let r = t.get_named_receptacles(&["market_feed", "missing"]);
410 assert_eq!(r.len(), 1);
411 }
412
413 #[test]
418 fn get_all_publishers_excludes_emit_only_sources() {
419 let t = trader();
420 assert_eq!(t.get_all_publishers().len(), 1);
422 assert!(t.get_all_emitters().is_empty());
423 }
424
425 #[test]
426 fn get_all_emitters_includes_only_emit_only_sources() {
427 let mut t = trader();
428 t.event_sources[0].emit_only = true;
429 assert!(t.get_all_publishers().is_empty());
430 assert_eq!(t.get_all_emitters().len(), 1);
431 }
432
433 #[test]
434 fn get_named_publishers_respects_emit_only_flag() {
435 let t = trader();
436 let p = t.get_named_publishers(&["trade_pub"]);
437 assert_eq!(p.len(), 1);
438 }
439
440 #[test]
441 fn get_named_emitters_excludes_publishers() {
442 let t = trader();
443 let e = t.get_named_emitters(&["trade_pub"]);
444 assert!(e.is_empty());
446 }
447
448 #[test]
453 fn supports_interface_returns_true_for_listed_iface() {
454 let mut t = trader();
455 t.supported_interfaces
456 .push("IDL:demo/Diagnostics:1.0".into());
457 assert!(t.supports_interface("IDL:demo/Diagnostics:1.0"));
458 }
459
460 #[test]
461 fn supports_interface_returns_false_for_unknown() {
462 let t = trader();
463 assert!(!t.supports_interface("IDL:demo/Unknown:1.0"));
464 }
465
466 #[test]
467 fn supported_interface_repo_ids_returns_full_list() {
468 let mut t = trader();
469 t.supported_interfaces.push("A".into());
470 t.supported_interfaces.push("B".into());
471 assert_eq!(
472 t.supported_interface_repo_ids(),
473 &["A".to_string(), "B".to_string()]
474 );
475 }
476}