1use alloc::string::String;
11use alloc::vec::Vec;
12
13pub trait ObjectRoot: Send + Sync {
20 fn oid(&self) -> u64;
22 fn repository_id(&self) -> &str;
24 fn is_modified(&self) -> bool;
26 fn is_deleted(&self) -> bool;
28}
29
30#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct ClassMetamodel {
37 pub name: String,
39 pub repository_id: String,
41 pub main_topic: String,
43 pub oid_field: String,
45 pub class_field: String,
47 pub full_oid_required: bool,
49 pub final_class: bool,
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct MultiAttributeMetamodel {
56 pub name: String,
58 pub topic: String,
60 pub target_field: String,
62 pub index_field: String,
64 pub key_fields: Vec<String>,
66}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
70pub struct MonoRelationMetamodel {
71 pub name: String,
73 pub topic: String,
75 pub target_fields: Vec<String>,
77 pub key_fields: Vec<String>,
79 pub full_oid_required: bool,
81 pub is_composition: bool,
83}
84
85pub mod default_mapping {
87 #[must_use]
89 pub fn topic_name(class: &str) -> alloc::string::String {
90 class.into()
91 }
92
93 pub const DEFAULT_OID_FIELD: &str = "oid";
95
96 pub const DEFAULT_CLASS_FIELD: &str = "class";
98
99 pub const DEFAULT_INDEX_FIELD: &str = "index";
101
102 #[must_use]
104 pub fn multi_topic(class: &str, attribute: &str) -> alloc::string::String {
105 alloc::format!("{class}.{attribute}")
106 }
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub enum DlrlEntityKind {
116 CacheFactory,
118 CacheBase,
120 Cache,
122 CacheAccess,
124 Contract,
126 Selection,
128 SelectionCriterion,
130 FilterCriterion,
132 QueryCriterion,
134 SelectionListener,
136 ObjectRoot,
138 ObjectHome,
140 Collection,
142 List,
144 Set,
146 StrMap,
148 IntMap,
150}
151
152impl DlrlEntityKind {
153 #[must_use]
155 pub const fn spec_name(self) -> &'static str {
156 match self {
157 Self::CacheFactory => "CacheFactory",
158 Self::CacheBase => "CacheBase",
159 Self::Cache => "Cache",
160 Self::CacheAccess => "CacheAccess",
161 Self::Contract => "Contract",
162 Self::Selection => "Selection",
163 Self::SelectionCriterion => "SelectionCriterion",
164 Self::FilterCriterion => "FilterCriterion",
165 Self::QueryCriterion => "QueryCriterion",
166 Self::SelectionListener => "SelectionListener",
167 Self::ObjectRoot => "ObjectRoot",
168 Self::ObjectHome => "ObjectHome",
169 Self::Collection => "Collection",
170 Self::List => "List",
171 Self::Set => "Set",
172 Self::StrMap => "StrMap",
173 Self::IntMap => "IntMap",
174 }
175 }
176
177 #[must_use]
179 pub fn all() -> [Self; 17] {
180 [
181 Self::CacheFactory,
182 Self::CacheBase,
183 Self::Cache,
184 Self::CacheAccess,
185 Self::Contract,
186 Self::Selection,
187 Self::SelectionCriterion,
188 Self::FilterCriterion,
189 Self::QueryCriterion,
190 Self::SelectionListener,
191 Self::ObjectRoot,
192 Self::ObjectHome,
193 Self::Collection,
194 Self::List,
195 Self::Set,
196 Self::StrMap,
197 Self::IntMap,
198 ]
199 }
200}
201
202#[derive(Debug, Clone, PartialEq, Eq)]
208pub enum DlrlException {
209 DcpsError {
211 reason: String,
213 },
214 BadHomeDefinition {
216 reason: String,
218 },
219 NotFound,
221 AlreadyExisting,
223 AlreadyDeleted,
225 PreconditionNotMet {
228 constraint: String,
230 },
231 NoSuchElement,
233 SqlError {
235 reason: String,
237 },
238}
239
240impl DlrlException {
241 #[must_use]
243 pub const fn repository_id(&self) -> &'static str {
244 match self {
245 Self::DcpsError { .. } => "IDL:omg.org/DLRL/DCPSError:1.0",
246 Self::BadHomeDefinition { .. } => "IDL:omg.org/DLRL/BadHomeDefinition:1.0",
247 Self::NotFound => "IDL:omg.org/DLRL/NotFound:1.0",
248 Self::AlreadyExisting => "IDL:omg.org/DLRL/AlreadyExisting:1.0",
249 Self::AlreadyDeleted => "IDL:omg.org/DLRL/AlreadyDeleted:1.0",
250 Self::PreconditionNotMet { .. } => "IDL:omg.org/DLRL/PreconditionNotMet:1.0",
251 Self::NoSuchElement => "IDL:omg.org/DLRL/NoSuchElement:1.0",
252 Self::SqlError { .. } => "IDL:omg.org/DLRL/SQLError:1.0",
253 }
254 }
255}
256
257#[derive(Debug, Clone, Copy, PartialEq, Eq)]
263pub enum CacheAttachmentState {
264 Detached,
266 AttachedSubscriber,
268 AttachedPublisher,
270 AttachedBoth,
272}
273
274#[derive(Debug, Clone, Copy, PartialEq, Eq)]
276pub enum CacheMode {
277 Transparent,
279 OnDemand,
281}
282
283#[derive(Debug, Clone, PartialEq, Eq)]
289pub struct QueryExpression {
290 pub expr: String,
292 pub params: Vec<String>,
294}
295
296impl QueryExpression {
297 #[must_use]
299 pub fn new(expr: impl Into<String>) -> Self {
300 Self {
301 expr: expr.into(),
302 params: Vec::new(),
303 }
304 }
305
306 pub fn add_param(&mut self, p: impl Into<String>) {
308 self.params.push(p.into());
309 }
310}
311
312#[cfg(test)]
313#[allow(clippy::expect_used)]
314mod tests {
315 use super::*;
316 use alloc::string::ToString;
317
318 struct DummyObject;
320 impl ObjectRoot for DummyObject {
321 fn oid(&self) -> u64 {
322 42
323 }
324 fn repository_id(&self) -> &str {
325 "IDL:demo/Dummy:1.0"
326 }
327 fn is_modified(&self) -> bool {
328 false
329 }
330 fn is_deleted(&self) -> bool {
331 false
332 }
333 }
334
335 #[test]
336 fn object_root_trait_callable() {
337 let o = DummyObject;
338 assert_eq!(o.oid(), 42);
339 assert_eq!(o.repository_id(), "IDL:demo/Dummy:1.0");
340 assert!(!o.is_modified());
341 assert!(!o.is_deleted());
342 }
343
344 #[test]
346 fn class_metamodel_default_fields() {
347 let c = ClassMetamodel {
348 name: "Trade".into(),
349 repository_id: "IDL:demo/Trade:1.0".into(),
350 main_topic: default_mapping::topic_name("Trade"),
351 oid_field: default_mapping::DEFAULT_OID_FIELD.into(),
352 class_field: default_mapping::DEFAULT_CLASS_FIELD.into(),
353 full_oid_required: false,
354 final_class: false,
355 };
356 assert_eq!(c.main_topic, "Trade");
357 assert_eq!(c.oid_field, "oid");
358 assert_eq!(c.class_field, "class");
359 }
360
361 #[test]
362 fn default_mapping_topic_name_uses_class() {
363 assert_eq!(default_mapping::topic_name("Trader"), "Trader");
364 }
365
366 #[test]
367 fn default_mapping_multi_topic_dot_separated() {
368 assert_eq!(
369 default_mapping::multi_topic("Order", "items"),
370 "Order.items"
371 );
372 }
373
374 #[test]
375 fn default_mapping_constants_match_spec() {
376 assert_eq!(default_mapping::DEFAULT_OID_FIELD, "oid");
377 assert_eq!(default_mapping::DEFAULT_CLASS_FIELD, "class");
378 assert_eq!(default_mapping::DEFAULT_INDEX_FIELD, "index");
379 }
380
381 #[test]
382 fn multi_attribute_metamodel_construct() {
383 let m = MultiAttributeMetamodel {
384 name: "items".into(),
385 topic: "Order.items".into(),
386 target_field: "value".into(),
387 index_field: "index".into(),
388 key_fields: alloc::vec!["order_id".into()],
389 };
390 assert_eq!(m.key_fields.len(), 1);
391 }
392
393 #[test]
394 fn mono_relation_metamodel_construct() {
395 let r = MonoRelationMetamodel {
396 name: "owner".into(),
397 topic: "Trade.owner".into(),
398 target_fields: alloc::vec!["trader_id".into()],
399 key_fields: alloc::vec!["trade_id".into()],
400 full_oid_required: true,
401 is_composition: false,
402 };
403 assert!(r.full_oid_required);
404 }
405
406 #[test]
408 fn dlrl_entity_kinds_count_17() {
409 assert_eq!(DlrlEntityKind::all().len(), 17);
410 }
411
412 #[test]
413 fn dlrl_entity_kinds_distinct() {
414 let kinds = DlrlEntityKind::all();
415 for (i, k1) in kinds.iter().enumerate() {
416 for k2 in kinds.iter().skip(i + 1) {
417 assert_ne!(k1, k2);
418 }
419 }
420 }
421
422 #[test]
423 fn dlrl_entity_kind_spec_names_match() {
424 assert_eq!(DlrlEntityKind::CacheFactory.spec_name(), "CacheFactory");
425 assert_eq!(DlrlEntityKind::ObjectRoot.spec_name(), "ObjectRoot");
426 assert_eq!(DlrlEntityKind::IntMap.spec_name(), "IntMap");
427 }
428
429 #[test]
431 fn dlrl_exception_repository_ids_distinct() {
432 let exceptions = [
433 DlrlException::DcpsError { reason: "x".into() },
434 DlrlException::BadHomeDefinition { reason: "x".into() },
435 DlrlException::NotFound,
436 DlrlException::AlreadyExisting,
437 DlrlException::AlreadyDeleted,
438 DlrlException::PreconditionNotMet {
439 constraint: "x".into(),
440 },
441 DlrlException::NoSuchElement,
442 DlrlException::SqlError { reason: "x".into() },
443 ];
444 let mut seen = std::collections::HashSet::new();
445 for e in &exceptions {
446 assert!(
447 seen.insert(e.repository_id()),
448 "duplicate: {}",
449 e.repository_id()
450 );
451 }
452 assert_eq!(seen.len(), 8);
453 }
454
455 #[test]
456 fn dlrl_exception_repo_id_omg_namespace() {
457 for e in [
458 DlrlException::NotFound,
459 DlrlException::AlreadyExisting,
460 DlrlException::NoSuchElement,
461 ] {
462 assert!(e.repository_id().starts_with("IDL:omg.org/DLRL/"));
463 }
464 }
465
466 #[test]
468 fn cache_attachment_states_distinct() {
469 assert_ne!(
470 CacheAttachmentState::Detached,
471 CacheAttachmentState::AttachedSubscriber
472 );
473 assert_ne!(
474 CacheAttachmentState::AttachedPublisher,
475 CacheAttachmentState::AttachedBoth
476 );
477 }
478
479 #[test]
480 fn cache_modes_distinct() {
481 assert_ne!(CacheMode::Transparent, CacheMode::OnDemand);
482 }
483
484 #[test]
486 fn query_expression_construct() {
487 let mut q = QueryExpression::new("price > ?");
488 q.add_param("100");
489 assert_eq!(q.expr, "price > ?");
490 assert_eq!(q.params, alloc::vec!["100".to_string()]);
491 }
492}