Skip to main content

zerodds_corba_ccm/
component_def.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! Component-Definition-Modell — Spec §6.6.
5//!
6//! Eine `ComponentDef` beschreibt einen Component-Type. Spec-konforme
7//! Felder:
8//!
9//! * `name` + `repository_id`.
10//! * Erbschaft (single-inheritance, plus Component supports).
11//! * Facets (`provides`-Ports — implementiert ein Iface).
12//! * Receptacles (`uses`-Ports — verlangt ein Iface; simplex/multiplex).
13//! * Event-Sources (`publishes`/`emits`).
14//! * Event-Sinks (`consumes`).
15//! * Attributes mit optional `setraises`/`getraises`.
16//! * Optional: PrimaryKey (Spec §6.7.2 fuer Keyed-Components).
17
18use alloc::string::String;
19use alloc::vec::Vec;
20
21/// Receptacle-Multiplicity — Spec §6.6.5.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23pub enum ReceptacleMultiplicity {
24    /// `uses` — single connection.
25    Simplex,
26    /// `uses multiple` — multi-connection.
27    Multiplex,
28}
29
30/// Facet (provides-Port) — Spec §6.6.4.
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct FacetDef {
33    /// Port-Name.
34    pub name: String,
35    /// Implementiertes Interface (Repository-ID).
36    pub interface_id: String,
37}
38
39/// Receptacle (uses-Port) — Spec §6.6.5.
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct ReceptacleDef {
42    /// Port-Name.
43    pub name: String,
44    /// Verlangtes Interface (Repository-ID).
45    pub interface_id: String,
46    /// Single oder multiple connections.
47    pub multiplicity: ReceptacleMultiplicity,
48}
49
50/// Event-Source (publishes/emits) — Spec §6.6.6.
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct EventSourceDef {
53    /// Port-Name.
54    pub name: String,
55    /// Event-Type (EventType-Repository-ID).
56    pub event_type_id: String,
57    /// `true` = `emits` (single subscriber); `false` = `publishes`
58    /// (multiple subscribers via Channel).
59    pub emit_only: bool,
60}
61
62/// Event-Sink (consumes) — Spec §6.6.7.
63#[derive(Debug, Clone, PartialEq, Eq)]
64pub struct EventSinkDef {
65    /// Port-Name.
66    pub name: String,
67    /// Event-Type (EventType-Repository-ID).
68    pub event_type_id: String,
69}
70
71/// Attribute — Spec §6.6.8.
72#[derive(Debug, Clone, PartialEq, Eq)]
73pub struct AttributeDef {
74    /// Attribut-Name.
75    pub name: String,
76    /// IDL-Type-Spec.
77    pub type_spec: String,
78    /// `true` = readonly.
79    pub readonly: bool,
80    /// `setraises`-Exception-IDs (leer fuer readonly).
81    pub set_raises: Vec<String>,
82    /// `getraises`-Exception-IDs.
83    pub get_raises: Vec<String>,
84}
85
86/// Component-Type-Definition.
87#[derive(Debug, Clone, PartialEq, Eq, Default)]
88pub struct ComponentDef {
89    /// Component-Name.
90    pub name: String,
91    /// Repository-ID.
92    pub repository_id: String,
93    /// Optionale Single-Inheritance auf einen Base-Component.
94    pub base_component: Option<String>,
95    /// `supports`-Iface-IDs.
96    pub supported_interfaces: Vec<String>,
97    /// Facets.
98    pub facets: Vec<FacetDef>,
99    /// Receptacles.
100    pub receptacles: Vec<ReceptacleDef>,
101    /// Event-Sources.
102    pub event_sources: Vec<EventSourceDef>,
103    /// Event-Sinks.
104    pub event_sinks: Vec<EventSinkDef>,
105    /// Attributes.
106    pub attributes: Vec<AttributeDef>,
107    /// PrimaryKey-Type-IDs (Spec §6.7.2 — Keyed-Component-Indikator).
108    pub primary_key: Vec<String>,
109}
110
111impl ComponentDef {
112    /// `true` wenn der Component Keyed ist (Primary-Key vorhanden).
113    #[must_use]
114    pub fn is_keyed(&self) -> bool {
115        !self.primary_key.is_empty()
116    }
117
118    /// Anzahl Ports (Facets + Receptacles + Events).
119    #[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    // ---------------------------------------------------------------
128    // §6.1.4 Component Identity (Operations)
129    // ---------------------------------------------------------------
130
131    /// Spec §6.1.4 — `is_equivalent_component_kind(repo_id)`.
132    /// Liefert `true` wenn der gegebene Repository-ID dem Component
133    /// (oder einer Base-Komponente in der Inheritance-Kette via
134    /// caller-supplied Resolver) entspricht.
135    #[must_use]
136    pub fn is_equivalent_component_kind(&self, repo_id: &str) -> bool {
137        self.repository_id == repo_id
138    }
139
140    /// Spec §6.1.4 — `get_component_def() -> ComponentIR::ComponentDef`.
141    /// Wir liefern hier die Repository-ID des ComponentDef-Eintrags
142    /// im IFR; Caller setzt darauf den IFR-Lookup auf.
143    #[must_use]
144    pub fn get_component_def_repo_id(&self) -> &str {
145        &self.repository_id
146    }
147
148    // ---------------------------------------------------------------
149    // §6.4.3 Navigation Interface (generic)
150    // ---------------------------------------------------------------
151
152    /// Spec §6.4.3 — `provide_facet(name) -> CORBA::Object`.
153    /// Liefert den `FacetDef` mit dem gegebenen Namen oder `None`.
154    /// Caller bindet das `interface_id` an die ORB-konkrete
155    /// Object-Reference.
156    #[must_use]
157    pub fn provide_facet(&self, name: &str) -> Option<&FacetDef> {
158        self.facets.iter().find(|f| f.name == name)
159    }
160
161    /// Spec §6.4.3 — `get_all_facets() -> FacetDescriptions`.
162    /// Liefert alle Facets als unveraenderbarer Slice.
163    #[must_use]
164    pub fn get_all_facets(&self) -> &[FacetDef] {
165        &self.facets
166    }
167
168    /// Spec §6.4.3 — `get_named_facets(names) -> FacetDescriptions`.
169    /// Liefert die Facets, deren Namen in der gegebenen Liste stehen
170    /// (Reihenfolge wie in `names`); fehlende Namen werden uebersprungen.
171    #[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    // ---------------------------------------------------------------
177    // §6.5.3 Receptacles Interface (generic)
178    // ---------------------------------------------------------------
179
180    /// Spec §6.5.3 — `get_all_receptacles() -> ReceptacleDescriptions`.
181    #[must_use]
182    pub fn get_all_receptacles(&self) -> &[ReceptacleDef] {
183        &self.receptacles
184    }
185
186    /// Spec §6.5.3 — `get_named_receptacles(names) -> ReceptacleDescriptions`.
187    #[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    // ---------------------------------------------------------------
196    // §6.6.8 Events Interface (generic)
197    // ---------------------------------------------------------------
198
199    /// Spec §6.6.8 — `get_all_publishers() -> PublisherDescriptions`.
200    /// Publisher = `event_sources` mit `emit_only == false`.
201    #[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    /// Spec §6.6.8 — `get_all_emitters() -> EmitterDescriptions`.
207    /// Emitter = `event_sources` mit `emit_only == true`.
208    #[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    /// Spec §6.6.8 — `get_named_publishers(names)`.
214    #[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    /// Spec §6.6.8 — `get_named_emitters(names)`.
227    #[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    // ---------------------------------------------------------------
240    // §6.4.5 Supported Interfaces — Runtime Narrow-Helper
241    // ---------------------------------------------------------------
242
243    /// Spec §6.4.5 — Type-Identity-Narrowing. Liefert `true` wenn die
244    /// Component das gegebene Interface unterstuetzt (`supports <I>`
245    /// in der Equivalent-IDL). Caller-Layer kombiniert dies mit dem
246    /// ORB-`is_a`-Predicate fuer die Object-Reference.
247    #[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    /// Spec §6.4.5 — alle unterstuetzten Interfaces (Type-Identity-
255    /// Side; Object-Reference-Widening bleibt ORB).
256    #[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    // ---------------------------------------------------------------
348    // §6.1.4 Component Identity
349    // ---------------------------------------------------------------
350
351    #[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    // ---------------------------------------------------------------
365    // §6.4.3 Navigation Interface
366    // ---------------------------------------------------------------
367
368    #[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    // ---------------------------------------------------------------
396    // §6.5.3 Receptacles Interface
397    // ---------------------------------------------------------------
398
399    #[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    // ---------------------------------------------------------------
414    // §6.6.8 Events Interface
415    // ---------------------------------------------------------------
416
417    #[test]
418    fn get_all_publishers_excludes_emit_only_sources() {
419        let t = trader();
420        // Default emit_only=false → publisher
421        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        // trade_pub has emit_only=false → not an emitter
445        assert!(e.is_empty());
446    }
447
448    // ---------------------------------------------------------------
449    // §6.4.5 Supported Interfaces
450    // ---------------------------------------------------------------
451
452    #[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}