Skip to main content

zerodds_corba_dds_bridge/
mapping.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! Mapping-Konfiguration zwischen CORBA-Objects und DDS-Topics.
5//!
6//! Ein `BridgeMapping` aggregiert mehrere `BridgeRoute`-Eintraege.
7//! Jede Route bindet ein CORBA-Object (per `repository_id` +
8//! `object_key`) bidirektional an ein oder mehrere DDS-Topics.
9
10use alloc::collections::BTreeMap;
11use alloc::string::String;
12use alloc::vec::Vec;
13
14/// Richtung einer Bridge-Route.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub enum Direction {
17    /// CORBA-Request → DDS-Sample (Servant-Modus).
18    CorbaToDds,
19    /// DDS-Sample → CORBA-Request (Forwarder-Modus).
20    DdsToCorba,
21    /// Bidirektional (`request topic` + `reply topic`).
22    Bidirectional,
23}
24
25/// QoS-Profile-Referenz fuer ein Topic — Caller-Layer-Resolution
26/// (z.B. ueber `crates/qos/`).
27#[derive(Debug, Clone, PartialEq, Eq, Default)]
28pub struct TopicQosRef {
29    /// Optional: Reliability-Kind ("RELIABLE"/"BEST_EFFORT").
30    pub reliability: Option<String>,
31    /// Optional: Durability-Kind ("VOLATILE"/"TRANSIENT_LOCAL"/...).
32    pub durability: Option<String>,
33    /// Optional: Profile-Name aus einem QoS-XML-Profile.
34    pub profile_name: Option<String>,
35}
36
37/// Mapping einer einzelnen IDL-Operation auf ein Topic.
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct OperationMapping {
40    /// IDL-Operation-Name (Wire-Form).
41    pub operation: String,
42    /// DDS-Topic fuer Request-Sample.
43    pub request_topic: String,
44    /// DDS-Topic fuer Reply-Sample (oder leer fuer fire-and-forget).
45    pub reply_topic: String,
46    /// QoS fuer beide Topics.
47    pub qos: TopicQosRef,
48}
49
50/// Eine Bridge-Route — bindet ein CORBA-Object an Operations-Topics.
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct BridgeRoute {
53    /// CORBA-Repository-ID des Object-Types (z.B.
54    /// `IDL:demo/Echo:1.0`).
55    pub repository_id: String,
56    /// CORBA-Object-Key (POA-Erstellt).
57    pub object_key: Vec<u8>,
58    /// Richtung der Bruecke.
59    pub direction: Direction,
60    /// Operation-Mapping pro IDL-Methode.
61    pub operations: Vec<OperationMapping>,
62}
63
64impl BridgeRoute {
65    /// Liefert das `OperationMapping` fuer einen Operation-Name.
66    #[must_use]
67    pub fn operation(&self, name: &str) -> Option<&OperationMapping> {
68        self.operations.iter().find(|o| o.operation == name)
69    }
70}
71
72/// Top-Level-Mapping mit O(1)-Lookup nach `(repository_id, object_key)`.
73#[derive(Debug, Clone, Default)]
74pub struct BridgeMapping {
75    routes: BTreeMap<RouteKey, BridgeRoute>,
76}
77
78#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
79struct RouteKey {
80    repository_id: String,
81    object_key: Vec<u8>,
82}
83
84impl BridgeMapping {
85    /// Konstruktor.
86    #[must_use]
87    pub fn new() -> Self {
88        Self::default()
89    }
90
91    /// Registriert eine Route.
92    pub fn add_route(&mut self, route: BridgeRoute) {
93        let key = RouteKey {
94            repository_id: route.repository_id.clone(),
95            object_key: route.object_key.clone(),
96        };
97        self.routes.insert(key, route);
98    }
99
100    /// Lookup einer Route.
101    #[must_use]
102    pub fn lookup(&self, repository_id: &str, object_key: &[u8]) -> Option<&BridgeRoute> {
103        let key = RouteKey {
104            repository_id: repository_id.into(),
105            object_key: object_key.to_vec(),
106        };
107        self.routes.get(&key)
108    }
109
110    /// Anzahl Routes.
111    #[must_use]
112    pub fn len(&self) -> usize {
113        self.routes.len()
114    }
115
116    /// `true` wenn leer.
117    #[must_use]
118    pub fn is_empty(&self) -> bool {
119        self.routes.is_empty()
120    }
121
122    /// Liste aller Routes.
123    #[must_use]
124    pub fn all_routes(&self) -> Vec<&BridgeRoute> {
125        self.routes.values().collect()
126    }
127}
128
129#[cfg(test)]
130#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
131mod tests {
132    use super::*;
133
134    fn echo_route() -> BridgeRoute {
135        BridgeRoute {
136            repository_id: "IDL:demo/Echo:1.0".into(),
137            object_key: alloc::vec![0xab],
138            direction: Direction::Bidirectional,
139            operations: alloc::vec![OperationMapping {
140                operation: "ping".into(),
141                request_topic: "demo/Echo/ping/Request".into(),
142                reply_topic: "demo/Echo/ping/Reply".into(),
143                qos: TopicQosRef {
144                    reliability: Some("RELIABLE".into()),
145                    durability: Some("VOLATILE".into()),
146                    profile_name: None,
147                },
148            }],
149        }
150    }
151
152    #[test]
153    fn add_and_lookup_route() {
154        let mut m = BridgeMapping::new();
155        m.add_route(echo_route());
156        let r = m.lookup("IDL:demo/Echo:1.0", &[0xab]).unwrap();
157        assert_eq!(r.direction, Direction::Bidirectional);
158        assert_eq!(m.len(), 1);
159    }
160
161    #[test]
162    fn lookup_unknown_yields_none() {
163        let m = BridgeMapping::new();
164        assert!(m.lookup("IDL:demo/Echo:1.0", &[0xff]).is_none());
165    }
166
167    #[test]
168    fn operation_mapping_by_name() {
169        let r = echo_route();
170        let op = r.operation("ping").unwrap();
171        assert_eq!(op.request_topic, "demo/Echo/ping/Request");
172        assert_eq!(op.reply_topic, "demo/Echo/ping/Reply");
173        assert!(r.operation("nope").is_none());
174    }
175
176    #[test]
177    fn add_route_replaces_existing() {
178        let mut m = BridgeMapping::new();
179        m.add_route(echo_route());
180        let mut r2 = echo_route();
181        r2.direction = Direction::CorbaToDds;
182        m.add_route(r2);
183        assert_eq!(m.len(), 1);
184        assert_eq!(
185            m.lookup("IDL:demo/Echo:1.0", &[0xab]).unwrap().direction,
186            Direction::CorbaToDds
187        );
188    }
189
190    #[test]
191    fn all_routes_listing() {
192        let mut m = BridgeMapping::new();
193        m.add_route(echo_route());
194        let mut other = echo_route();
195        other.repository_id = "IDL:demo/Other:1.0".into();
196        m.add_route(other);
197        assert_eq!(m.all_routes().len(), 2);
198    }
199
200    #[test]
201    fn direction_variants_distinct() {
202        assert_ne!(Direction::CorbaToDds, Direction::DdsToCorba);
203        assert_ne!(Direction::CorbaToDds, Direction::Bidirectional);
204    }
205}