use alloc::string::String;
use alloc::vec::Vec;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ProfileId(pub [u8; 16]);
impl ProfileId {
#[must_use]
pub const fn nil() -> Self {
Self([0u8; 16])
}
}
impl Default for ProfileId {
fn default() -> Self {
Self::nil()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PortDirection {
#[default]
In,
Out,
InOut,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct PortProfile {
pub id: ProfileId,
pub name: String,
pub data_type: String,
pub direction: PortDirection,
pub properties: Vec<(String, String)>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ConnectorProfile {
pub id: ProfileId,
pub name: String,
pub port_ids: Vec<ProfileId>,
pub properties: Vec<(String, String)>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ComponentProfile {
pub id: ProfileId,
pub type_name: String,
pub instance_name: String,
pub vendor: String,
pub version: String,
pub ports: Vec<PortProfile>,
pub connectors: Vec<ConnectorProfile>,
pub properties: Vec<(String, String)>,
}
pub trait Introspection {
fn get_component_profile(&self) -> &ComponentProfile;
fn get_port_profile(&self, id: &ProfileId) -> Option<&PortProfile> {
self.get_component_profile()
.ports
.iter()
.find(|p| &p.id == id)
}
fn get_connector_profile(&self, id: &ProfileId) -> Option<&ConnectorProfile> {
self.get_component_profile()
.connectors
.iter()
.find(|c| &c.id == id)
}
fn get_ports(&self) -> &[PortProfile] {
&self.get_component_profile().ports
}
fn get_connectors(&self) -> &[ConnectorProfile] {
&self.get_component_profile().connectors
}
}
#[cfg(test)]
#[allow(clippy::expect_used)]
mod tests {
use super::*;
fn pid(b: u8) -> ProfileId {
let mut a = [0u8; 16];
a[15] = b;
ProfileId(a)
}
fn sample_port(b: u8, name: &str, dir: PortDirection) -> PortProfile {
PortProfile {
id: pid(b),
name: name.into(),
data_type: "geometry::Pose".into(),
direction: dir,
properties: Vec::new(),
}
}
struct StubComponent {
profile: ComponentProfile,
}
impl Introspection for StubComponent {
fn get_component_profile(&self) -> &ComponentProfile {
&self.profile
}
}
fn build_stub() -> StubComponent {
let p1 = sample_port(1, "in_port", PortDirection::In);
let p2 = sample_port(2, "out_port", PortDirection::Out);
let conn = ConnectorProfile {
id: pid(10),
name: "loop".into(),
port_ids: alloc::vec![pid(1), pid(2)],
properties: Vec::new(),
};
let comp = ComponentProfile {
id: pid(99),
type_name: "robotics::Localizer".into(),
instance_name: "loc1".into(),
vendor: "ZeroDDS".into(),
version: "1.0".into(),
ports: alloc::vec![p1, p2],
connectors: alloc::vec![conn],
properties: Vec::new(),
};
StubComponent { profile: comp }
}
#[test]
fn get_component_profile_returns_component() {
let s = build_stub();
assert_eq!(s.get_component_profile().instance_name, "loc1");
}
#[test]
fn get_port_profile_returns_some_when_known() {
let s = build_stub();
let p = s.get_port_profile(&pid(1)).expect("port present");
assert_eq!(p.name, "in_port");
assert_eq!(p.direction, PortDirection::In);
}
#[test]
fn get_port_profile_returns_none_when_unknown() {
let s = build_stub();
assert!(s.get_port_profile(&pid(99)).is_none());
}
#[test]
fn get_connector_profile_returns_known_connector() {
let s = build_stub();
let c = s.get_connector_profile(&pid(10)).expect("connector");
assert_eq!(c.name, "loop");
assert_eq!(c.port_ids.len(), 2);
}
#[test]
fn get_ports_returns_all_two_ports() {
let s = build_stub();
assert_eq!(s.get_ports().len(), 2);
}
#[test]
fn get_connectors_returns_one_connector() {
let s = build_stub();
assert_eq!(s.get_connectors().len(), 1);
}
#[test]
fn nil_profile_id_has_zero_bytes() {
assert_eq!(ProfileId::nil().0, [0u8; 16]);
}
#[test]
fn default_port_direction_is_in() {
assert_eq!(PortDirection::default(), PortDirection::In);
}
#[test]
fn introspection_default_methods_compose_correctly() {
let s = build_stub();
assert_eq!(s.get_ports().len(), s.get_component_profile().ports.len());
}
#[test]
fn component_profile_field_round_trip() {
let cp = ComponentProfile {
id: pid(1),
type_name: "T".into(),
instance_name: "I".into(),
vendor: "V".into(),
version: "1.0".into(),
ports: Vec::new(),
connectors: Vec::new(),
properties: alloc::vec![("k".into(), "v".into())],
};
assert_eq!(
cp.properties[0],
(
alloc::string::String::from("k"),
alloc::string::String::from("v")
)
);
}
}