use alloc::string::String;
use alloc::vec::Vec;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ReceptacleMultiplicity {
Simplex,
Multiplex,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FacetDef {
pub name: String,
pub interface_id: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReceptacleDef {
pub name: String,
pub interface_id: String,
pub multiplicity: ReceptacleMultiplicity,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EventSourceDef {
pub name: String,
pub event_type_id: String,
pub emit_only: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EventSinkDef {
pub name: String,
pub event_type_id: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AttributeDef {
pub name: String,
pub type_spec: String,
pub readonly: bool,
pub set_raises: Vec<String>,
pub get_raises: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ComponentDef {
pub name: String,
pub repository_id: String,
pub base_component: Option<String>,
pub supported_interfaces: Vec<String>,
pub facets: Vec<FacetDef>,
pub receptacles: Vec<ReceptacleDef>,
pub event_sources: Vec<EventSourceDef>,
pub event_sinks: Vec<EventSinkDef>,
pub attributes: Vec<AttributeDef>,
pub primary_key: Vec<String>,
}
impl ComponentDef {
#[must_use]
pub fn is_keyed(&self) -> bool {
!self.primary_key.is_empty()
}
#[must_use]
pub fn port_count(&self) -> usize {
self.facets.len()
+ self.receptacles.len()
+ self.event_sources.len()
+ self.event_sinks.len()
}
#[must_use]
pub fn is_equivalent_component_kind(&self, repo_id: &str) -> bool {
self.repository_id == repo_id
}
#[must_use]
pub fn get_component_def_repo_id(&self) -> &str {
&self.repository_id
}
#[must_use]
pub fn provide_facet(&self, name: &str) -> Option<&FacetDef> {
self.facets.iter().find(|f| f.name == name)
}
#[must_use]
pub fn get_all_facets(&self) -> &[FacetDef] {
&self.facets
}
#[must_use]
pub fn get_named_facets(&self, names: &[&str]) -> Vec<&FacetDef> {
names.iter().filter_map(|n| self.provide_facet(n)).collect()
}
#[must_use]
pub fn get_all_receptacles(&self) -> &[ReceptacleDef] {
&self.receptacles
}
#[must_use]
pub fn get_named_receptacles(&self, names: &[&str]) -> Vec<&ReceptacleDef> {
names
.iter()
.filter_map(|n| self.receptacles.iter().find(|r| r.name == *n))
.collect()
}
#[must_use]
pub fn get_all_publishers(&self) -> Vec<&EventSourceDef> {
self.event_sources.iter().filter(|s| !s.emit_only).collect()
}
#[must_use]
pub fn get_all_emitters(&self) -> Vec<&EventSourceDef> {
self.event_sources.iter().filter(|s| s.emit_only).collect()
}
#[must_use]
pub fn get_named_publishers(&self, names: &[&str]) -> Vec<&EventSourceDef> {
names
.iter()
.filter_map(|n| {
self.event_sources
.iter()
.find(|s| !s.emit_only && s.name == *n)
})
.collect()
}
#[must_use]
pub fn get_named_emitters(&self, names: &[&str]) -> Vec<&EventSourceDef> {
names
.iter()
.filter_map(|n| {
self.event_sources
.iter()
.find(|s| s.emit_only && s.name == *n)
})
.collect()
}
#[must_use]
pub fn supports_interface(&self, interface_repo_id: &str) -> bool {
self.supported_interfaces
.iter()
.any(|i| i == interface_repo_id)
}
#[must_use]
pub fn supported_interface_repo_ids(&self) -> &[String] {
&self.supported_interfaces
}
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
fn trader() -> ComponentDef {
ComponentDef {
name: "Trader".into(),
repository_id: "IDL:demo/Trader:1.0".into(),
base_component: None,
supported_interfaces: alloc::vec![],
facets: alloc::vec![FacetDef {
name: "control".into(),
interface_id: "IDL:demo/Control:1.0".into(),
}],
receptacles: alloc::vec![ReceptacleDef {
name: "market_feed".into(),
interface_id: "IDL:demo/MarketFeed:1.0".into(),
multiplicity: ReceptacleMultiplicity::Simplex,
}],
event_sources: alloc::vec![EventSourceDef {
name: "trade_pub".into(),
event_type_id: "IDL:demo/Trade:1.0".into(),
emit_only: false,
}],
event_sinks: alloc::vec![EventSinkDef {
name: "alarm_sink".into(),
event_type_id: "IDL:demo/Alarm:1.0".into(),
}],
attributes: alloc::vec![AttributeDef {
name: "max_volume".into(),
type_spec: "long long".into(),
readonly: false,
set_raises: alloc::vec![],
get_raises: alloc::vec![],
}],
primary_key: alloc::vec![],
}
}
#[test]
fn trader_has_4_ports() {
let t = trader();
assert_eq!(t.port_count(), 4);
}
#[test]
fn unkeyed_component_is_not_keyed() {
assert!(!trader().is_keyed());
}
#[test]
fn keyed_component_detected() {
let mut t = trader();
t.primary_key.push("IDL:demo/TraderKey:1.0".into());
assert!(t.is_keyed());
}
#[test]
fn receptacle_multiplicity_distinct() {
assert_ne!(
ReceptacleMultiplicity::Simplex,
ReceptacleMultiplicity::Multiplex
);
}
#[test]
fn event_source_emit_only_default_false() {
let t = trader();
assert!(!t.event_sources[0].emit_only);
}
#[test]
fn attribute_readonly_skips_set_raises() {
let a = AttributeDef {
name: "version".into(),
type_spec: "string".into(),
readonly: true,
set_raises: alloc::vec![],
get_raises: alloc::vec!["IDL:demo/Bad:1.0".into()],
};
assert!(a.readonly);
assert!(a.set_raises.is_empty());
}
#[test]
fn is_equivalent_component_kind_matches_repo_id() {
let t = trader();
assert!(t.is_equivalent_component_kind("IDL:demo/Trader:1.0"));
assert!(!t.is_equivalent_component_kind("IDL:demo/Other:1.0"));
}
#[test]
fn get_component_def_repo_id_returns_repo_id() {
let t = trader();
assert_eq!(t.get_component_def_repo_id(), "IDL:demo/Trader:1.0");
}
#[test]
fn provide_facet_returns_facet_by_name() {
let t = trader();
let f = t.provide_facet("control").expect("control facet");
assert_eq!(f.name, "control");
}
#[test]
fn provide_facet_returns_none_for_unknown() {
let t = trader();
assert!(t.provide_facet("nonexistent").is_none());
}
#[test]
fn get_all_facets_returns_complete_list() {
let t = trader();
assert_eq!(t.get_all_facets().len(), 1);
}
#[test]
fn get_named_facets_filters_by_names() {
let t = trader();
let f = t.get_named_facets(&["control", "missing"]);
assert_eq!(f.len(), 1);
assert_eq!(f[0].name, "control");
}
#[test]
fn get_all_receptacles_returns_complete_list() {
let t = trader();
assert_eq!(t.get_all_receptacles().len(), 1);
assert_eq!(t.get_all_receptacles()[0].name, "market_feed");
}
#[test]
fn get_named_receptacles_filters() {
let t = trader();
let r = t.get_named_receptacles(&["market_feed", "missing"]);
assert_eq!(r.len(), 1);
}
#[test]
fn get_all_publishers_excludes_emit_only_sources() {
let t = trader();
assert_eq!(t.get_all_publishers().len(), 1);
assert!(t.get_all_emitters().is_empty());
}
#[test]
fn get_all_emitters_includes_only_emit_only_sources() {
let mut t = trader();
t.event_sources[0].emit_only = true;
assert!(t.get_all_publishers().is_empty());
assert_eq!(t.get_all_emitters().len(), 1);
}
#[test]
fn get_named_publishers_respects_emit_only_flag() {
let t = trader();
let p = t.get_named_publishers(&["trade_pub"]);
assert_eq!(p.len(), 1);
}
#[test]
fn get_named_emitters_excludes_publishers() {
let t = trader();
let e = t.get_named_emitters(&["trade_pub"]);
assert!(e.is_empty());
}
#[test]
fn supports_interface_returns_true_for_listed_iface() {
let mut t = trader();
t.supported_interfaces
.push("IDL:demo/Diagnostics:1.0".into());
assert!(t.supports_interface("IDL:demo/Diagnostics:1.0"));
}
#[test]
fn supports_interface_returns_false_for_unknown() {
let t = trader();
assert!(!t.supports_interface("IDL:demo/Unknown:1.0"));
}
#[test]
fn supported_interface_repo_ids_returns_full_list() {
let mut t = trader();
t.supported_interfaces.push("A".into());
t.supported_interfaces.push("B".into());
assert_eq!(
t.supported_interface_repo_ids(),
&["A".to_string(), "B".to_string()]
);
}
}