use alloc::string::String;
use alloc::vec::Vec;
pub trait ObjectRoot: Send + Sync {
fn oid(&self) -> u64;
fn repository_id(&self) -> &str;
fn is_modified(&self) -> bool;
fn is_deleted(&self) -> bool;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ClassMetamodel {
pub name: String,
pub repository_id: String,
pub main_topic: String,
pub oid_field: String,
pub class_field: String,
pub full_oid_required: bool,
pub final_class: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MultiAttributeMetamodel {
pub name: String,
pub topic: String,
pub target_field: String,
pub index_field: String,
pub key_fields: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MonoRelationMetamodel {
pub name: String,
pub topic: String,
pub target_fields: Vec<String>,
pub key_fields: Vec<String>,
pub full_oid_required: bool,
pub is_composition: bool,
}
pub mod default_mapping {
#[must_use]
pub fn topic_name(class: &str) -> alloc::string::String {
class.into()
}
pub const DEFAULT_OID_FIELD: &str = "oid";
pub const DEFAULT_CLASS_FIELD: &str = "class";
pub const DEFAULT_INDEX_FIELD: &str = "index";
#[must_use]
pub fn multi_topic(class: &str, attribute: &str) -> alloc::string::String {
alloc::format!("{class}.{attribute}")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DlrlEntityKind {
CacheFactory,
CacheBase,
Cache,
CacheAccess,
Contract,
Selection,
SelectionCriterion,
FilterCriterion,
QueryCriterion,
SelectionListener,
ObjectRoot,
ObjectHome,
Collection,
List,
Set,
StrMap,
IntMap,
}
impl DlrlEntityKind {
#[must_use]
pub const fn spec_name(self) -> &'static str {
match self {
Self::CacheFactory => "CacheFactory",
Self::CacheBase => "CacheBase",
Self::Cache => "Cache",
Self::CacheAccess => "CacheAccess",
Self::Contract => "Contract",
Self::Selection => "Selection",
Self::SelectionCriterion => "SelectionCriterion",
Self::FilterCriterion => "FilterCriterion",
Self::QueryCriterion => "QueryCriterion",
Self::SelectionListener => "SelectionListener",
Self::ObjectRoot => "ObjectRoot",
Self::ObjectHome => "ObjectHome",
Self::Collection => "Collection",
Self::List => "List",
Self::Set => "Set",
Self::StrMap => "StrMap",
Self::IntMap => "IntMap",
}
}
#[must_use]
pub fn all() -> [Self; 17] {
[
Self::CacheFactory,
Self::CacheBase,
Self::Cache,
Self::CacheAccess,
Self::Contract,
Self::Selection,
Self::SelectionCriterion,
Self::FilterCriterion,
Self::QueryCriterion,
Self::SelectionListener,
Self::ObjectRoot,
Self::ObjectHome,
Self::Collection,
Self::List,
Self::Set,
Self::StrMap,
Self::IntMap,
]
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DlrlException {
DcpsError {
reason: String,
},
BadHomeDefinition {
reason: String,
},
NotFound,
AlreadyExisting,
AlreadyDeleted,
PreconditionNotMet {
constraint: String,
},
NoSuchElement,
SqlError {
reason: String,
},
}
impl DlrlException {
#[must_use]
pub const fn repository_id(&self) -> &'static str {
match self {
Self::DcpsError { .. } => "IDL:omg.org/DLRL/DCPSError:1.0",
Self::BadHomeDefinition { .. } => "IDL:omg.org/DLRL/BadHomeDefinition:1.0",
Self::NotFound => "IDL:omg.org/DLRL/NotFound:1.0",
Self::AlreadyExisting => "IDL:omg.org/DLRL/AlreadyExisting:1.0",
Self::AlreadyDeleted => "IDL:omg.org/DLRL/AlreadyDeleted:1.0",
Self::PreconditionNotMet { .. } => "IDL:omg.org/DLRL/PreconditionNotMet:1.0",
Self::NoSuchElement => "IDL:omg.org/DLRL/NoSuchElement:1.0",
Self::SqlError { .. } => "IDL:omg.org/DLRL/SQLError:1.0",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CacheAttachmentState {
Detached,
AttachedSubscriber,
AttachedPublisher,
AttachedBoth,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CacheMode {
Transparent,
OnDemand,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct QueryExpression {
pub expr: String,
pub params: Vec<String>,
}
impl QueryExpression {
#[must_use]
pub fn new(expr: impl Into<String>) -> Self {
Self {
expr: expr.into(),
params: Vec::new(),
}
}
pub fn add_param(&mut self, p: impl Into<String>) {
self.params.push(p.into());
}
}
#[cfg(test)]
#[allow(clippy::expect_used)]
mod tests {
use super::*;
use alloc::string::ToString;
struct DummyObject;
impl ObjectRoot for DummyObject {
fn oid(&self) -> u64 {
42
}
fn repository_id(&self) -> &str {
"IDL:demo/Dummy:1.0"
}
fn is_modified(&self) -> bool {
false
}
fn is_deleted(&self) -> bool {
false
}
}
#[test]
fn object_root_trait_callable() {
let o = DummyObject;
assert_eq!(o.oid(), 42);
assert_eq!(o.repository_id(), "IDL:demo/Dummy:1.0");
assert!(!o.is_modified());
assert!(!o.is_deleted());
}
#[test]
fn class_metamodel_default_fields() {
let c = ClassMetamodel {
name: "Trade".into(),
repository_id: "IDL:demo/Trade:1.0".into(),
main_topic: default_mapping::topic_name("Trade"),
oid_field: default_mapping::DEFAULT_OID_FIELD.into(),
class_field: default_mapping::DEFAULT_CLASS_FIELD.into(),
full_oid_required: false,
final_class: false,
};
assert_eq!(c.main_topic, "Trade");
assert_eq!(c.oid_field, "oid");
assert_eq!(c.class_field, "class");
}
#[test]
fn default_mapping_topic_name_uses_class() {
assert_eq!(default_mapping::topic_name("Trader"), "Trader");
}
#[test]
fn default_mapping_multi_topic_dot_separated() {
assert_eq!(
default_mapping::multi_topic("Order", "items"),
"Order.items"
);
}
#[test]
fn default_mapping_constants_match_spec() {
assert_eq!(default_mapping::DEFAULT_OID_FIELD, "oid");
assert_eq!(default_mapping::DEFAULT_CLASS_FIELD, "class");
assert_eq!(default_mapping::DEFAULT_INDEX_FIELD, "index");
}
#[test]
fn multi_attribute_metamodel_construct() {
let m = MultiAttributeMetamodel {
name: "items".into(),
topic: "Order.items".into(),
target_field: "value".into(),
index_field: "index".into(),
key_fields: alloc::vec!["order_id".into()],
};
assert_eq!(m.key_fields.len(), 1);
}
#[test]
fn mono_relation_metamodel_construct() {
let r = MonoRelationMetamodel {
name: "owner".into(),
topic: "Trade.owner".into(),
target_fields: alloc::vec!["trader_id".into()],
key_fields: alloc::vec!["trade_id".into()],
full_oid_required: true,
is_composition: false,
};
assert!(r.full_oid_required);
}
#[test]
fn dlrl_entity_kinds_count_17() {
assert_eq!(DlrlEntityKind::all().len(), 17);
}
#[test]
fn dlrl_entity_kinds_distinct() {
let kinds = DlrlEntityKind::all();
for (i, k1) in kinds.iter().enumerate() {
for k2 in kinds.iter().skip(i + 1) {
assert_ne!(k1, k2);
}
}
}
#[test]
fn dlrl_entity_kind_spec_names_match() {
assert_eq!(DlrlEntityKind::CacheFactory.spec_name(), "CacheFactory");
assert_eq!(DlrlEntityKind::ObjectRoot.spec_name(), "ObjectRoot");
assert_eq!(DlrlEntityKind::IntMap.spec_name(), "IntMap");
}
#[test]
fn dlrl_exception_repository_ids_distinct() {
let exceptions = [
DlrlException::DcpsError { reason: "x".into() },
DlrlException::BadHomeDefinition { reason: "x".into() },
DlrlException::NotFound,
DlrlException::AlreadyExisting,
DlrlException::AlreadyDeleted,
DlrlException::PreconditionNotMet {
constraint: "x".into(),
},
DlrlException::NoSuchElement,
DlrlException::SqlError { reason: "x".into() },
];
let mut seen = std::collections::HashSet::new();
for e in &exceptions {
assert!(
seen.insert(e.repository_id()),
"duplicate: {}",
e.repository_id()
);
}
assert_eq!(seen.len(), 8);
}
#[test]
fn dlrl_exception_repo_id_omg_namespace() {
for e in [
DlrlException::NotFound,
DlrlException::AlreadyExisting,
DlrlException::NoSuchElement,
] {
assert!(e.repository_id().starts_with("IDL:omg.org/DLRL/"));
}
}
#[test]
fn cache_attachment_states_distinct() {
assert_ne!(
CacheAttachmentState::Detached,
CacheAttachmentState::AttachedSubscriber
);
assert_ne!(
CacheAttachmentState::AttachedPublisher,
CacheAttachmentState::AttachedBoth
);
}
#[test]
fn cache_modes_distinct() {
assert_ne!(CacheMode::Transparent, CacheMode::OnDemand);
}
#[test]
fn query_expression_construct() {
let mut q = QueryExpression::new("price > ?");
q.add_param("100");
assert_eq!(q.expr, "price > ?");
assert_eq!(q.params, alloc::vec!["100".to_string()]);
}
}