1use std::fmt;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
53#[repr(u16)]
54#[non_exhaustive]
55pub enum Domain {
56 #[default]
58 General = 0,
59
60 GraphAnalytics = 1,
63
64 StatisticalML = 2,
67
68 Compliance = 3,
71
72 RiskManagement = 4,
75
76 OrderMatching = 5,
79
80 MarketData = 6,
83
84 Settlement = 7,
87
88 Accounting = 8,
91
92 NetworkAnalysis = 9,
95
96 FraudDetection = 10,
99
100 TimeSeries = 11,
103
104 Simulation = 12,
107
108 Banking = 13,
111
112 BehavioralAnalytics = 14,
115
116 ProcessIntelligence = 15,
119
120 Clearing = 16,
123
124 TreasuryManagement = 17,
127
128 PaymentProcessing = 18,
131
132 FinancialAudit = 19,
135
136 Custom = 100,
139}
140
141impl Domain {
142 pub const RANGE_SIZE: u64 = 100;
144
145 pub const CUSTOM_BASE: u64 = 10000;
147
148 #[inline]
163 pub const fn base_type_id(&self) -> u64 {
164 match self {
165 Self::General => 0,
166 Self::GraphAnalytics => 100,
167 Self::StatisticalML => 200,
168 Self::Compliance => 300,
169 Self::RiskManagement => 400,
170 Self::OrderMatching => 500,
171 Self::MarketData => 600,
172 Self::Settlement => 700,
173 Self::Accounting => 800,
174 Self::NetworkAnalysis => 900,
175 Self::FraudDetection => 1000,
176 Self::TimeSeries => 1100,
177 Self::Simulation => 1200,
178 Self::Banking => 1300,
179 Self::BehavioralAnalytics => 1400,
180 Self::ProcessIntelligence => 1500,
181 Self::Clearing => 1600,
182 Self::TreasuryManagement => 1700,
183 Self::PaymentProcessing => 1800,
184 Self::FinancialAudit => 1900,
185 Self::Custom => Self::CUSTOM_BASE,
186 }
187 }
188
189 #[inline]
200 pub const fn max_type_id(&self) -> u64 {
201 match self {
202 Self::Custom => u64::MAX,
203 _ => self.base_type_id() + Self::RANGE_SIZE - 1,
204 }
205 }
206
207 #[inline]
219 pub const fn contains_type_id(&self, type_id: u64) -> bool {
220 type_id >= self.base_type_id() && type_id <= self.max_type_id()
221 }
222
223 pub const fn from_type_id(type_id: u64) -> Option<Self> {
236 match type_id {
237 0..=99 => Some(Self::General),
238 100..=199 => Some(Self::GraphAnalytics),
239 200..=299 => Some(Self::StatisticalML),
240 300..=399 => Some(Self::Compliance),
241 400..=499 => Some(Self::RiskManagement),
242 500..=599 => Some(Self::OrderMatching),
243 600..=699 => Some(Self::MarketData),
244 700..=799 => Some(Self::Settlement),
245 800..=899 => Some(Self::Accounting),
246 900..=999 => Some(Self::NetworkAnalysis),
247 1000..=1099 => Some(Self::FraudDetection),
248 1100..=1199 => Some(Self::TimeSeries),
249 1200..=1299 => Some(Self::Simulation),
250 1300..=1399 => Some(Self::Banking),
251 1400..=1499 => Some(Self::BehavioralAnalytics),
252 1500..=1599 => Some(Self::ProcessIntelligence),
253 1600..=1699 => Some(Self::Clearing),
254 1700..=1799 => Some(Self::TreasuryManagement),
255 1800..=1899 => Some(Self::PaymentProcessing),
256 1900..=1999 => Some(Self::FinancialAudit),
257 10000.. => Some(Self::Custom),
258 _ => None,
259 }
260 }
261
262 #[allow(clippy::should_implement_trait)]
281 pub fn from_str(s: &str) -> Option<Self> {
282 let normalized: String = s
283 .chars()
284 .filter(|c| c.is_alphanumeric())
285 .collect::<String>()
286 .to_lowercase();
287
288 match normalized.as_str() {
289 "general" | "gen" => Some(Self::General),
290 "graphanalytics" | "graph" => Some(Self::GraphAnalytics),
291 "statisticalml" | "ml" | "machinelearning" => Some(Self::StatisticalML),
292 "compliance" | "comp" | "regulatory" => Some(Self::Compliance),
293 "riskmanagement" | "risk" => Some(Self::RiskManagement),
294 "ordermatching" | "orders" | "order" | "matching" => Some(Self::OrderMatching),
295 "marketdata" | "market" | "mktdata" => Some(Self::MarketData),
296 "settlement" | "settle" => Some(Self::Settlement),
297 "accounting" | "acct" | "ledger" => Some(Self::Accounting),
298 "networkanalysis" | "network" | "netanalysis" => Some(Self::NetworkAnalysis),
299 "frauddetection" | "fraud" | "aml" => Some(Self::FraudDetection),
300 "timeseries" | "ts" | "temporal" => Some(Self::TimeSeries),
301 "simulation" | "sim" | "montecarlo" => Some(Self::Simulation),
302 "banking" | "bank" => Some(Self::Banking),
303 "behavioralanalytics" | "behavioral" | "behavior" => Some(Self::BehavioralAnalytics),
304 "processintelligence" | "process" | "processmining" => Some(Self::ProcessIntelligence),
305 "clearing" | "ccp" => Some(Self::Clearing),
306 "treasurymanagement" | "treasury" => Some(Self::TreasuryManagement),
307 "paymentprocessing" | "payment" | "payments" => Some(Self::PaymentProcessing),
308 "financialaudit" | "audit" => Some(Self::FinancialAudit),
309 "custom" => Some(Self::Custom),
310 _ => None,
311 }
312 }
313
314 #[inline]
318 pub const fn as_str(&self) -> &'static str {
319 match self {
320 Self::General => "General",
321 Self::GraphAnalytics => "GraphAnalytics",
322 Self::StatisticalML => "StatisticalML",
323 Self::Compliance => "Compliance",
324 Self::RiskManagement => "RiskManagement",
325 Self::OrderMatching => "OrderMatching",
326 Self::MarketData => "MarketData",
327 Self::Settlement => "Settlement",
328 Self::Accounting => "Accounting",
329 Self::NetworkAnalysis => "NetworkAnalysis",
330 Self::FraudDetection => "FraudDetection",
331 Self::TimeSeries => "TimeSeries",
332 Self::Simulation => "Simulation",
333 Self::Banking => "Banking",
334 Self::BehavioralAnalytics => "BehavioralAnalytics",
335 Self::ProcessIntelligence => "ProcessIntelligence",
336 Self::Clearing => "Clearing",
337 Self::TreasuryManagement => "TreasuryManagement",
338 Self::PaymentProcessing => "PaymentProcessing",
339 Self::FinancialAudit => "FinancialAudit",
340 Self::Custom => "Custom",
341 }
342 }
343
344 pub const fn all_standard() -> &'static [Domain] {
346 &[
347 Self::General,
348 Self::GraphAnalytics,
349 Self::StatisticalML,
350 Self::Compliance,
351 Self::RiskManagement,
352 Self::OrderMatching,
353 Self::MarketData,
354 Self::Settlement,
355 Self::Accounting,
356 Self::NetworkAnalysis,
357 Self::FraudDetection,
358 Self::TimeSeries,
359 Self::Simulation,
360 Self::Banking,
361 Self::BehavioralAnalytics,
362 Self::ProcessIntelligence,
363 Self::Clearing,
364 Self::TreasuryManagement,
365 Self::PaymentProcessing,
366 Self::FinancialAudit,
367 ]
368 }
369}
370
371impl fmt::Display for Domain {
372 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
373 write!(f, "{}", self.as_str())
374 }
375}
376
377impl std::str::FromStr for Domain {
378 type Err = DomainParseError;
379
380 fn from_str(s: &str) -> Result<Self, Self::Err> {
381 Domain::from_str(s).ok_or_else(|| DomainParseError(s.to_string()))
382 }
383}
384
385#[derive(Debug, Clone)]
387pub struct DomainParseError(pub String);
388
389impl fmt::Display for DomainParseError {
390 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
391 write!(f, "unknown domain: '{}'", self.0)
392 }
393}
394
395impl std::error::Error for DomainParseError {}
396
397pub trait DomainMessage: crate::message::RingMessage {
419 fn domain() -> Domain;
421
422 fn domain_type_id() -> u64 {
426 Self::message_type().saturating_sub(Self::domain().base_type_id())
427 }
428
429 fn is_valid_domain_type_id() -> bool {
431 Self::domain().contains_type_id(Self::message_type())
432 }
433}
434
435#[cfg(test)]
436mod tests {
437 use super::*;
438
439 #[test]
440 fn test_domain_base_type_ids() {
441 assert_eq!(Domain::General.base_type_id(), 0);
442 assert_eq!(Domain::GraphAnalytics.base_type_id(), 100);
443 assert_eq!(Domain::StatisticalML.base_type_id(), 200);
444 assert_eq!(Domain::OrderMatching.base_type_id(), 500);
445 assert_eq!(Domain::Custom.base_type_id(), 10000);
446 }
447
448 #[test]
449 fn test_domain_max_type_ids() {
450 assert_eq!(Domain::General.max_type_id(), 99);
451 assert_eq!(Domain::OrderMatching.max_type_id(), 599);
452 assert_eq!(Domain::Custom.max_type_id(), u64::MAX);
453 }
454
455 #[test]
456 fn test_domain_contains_type_id() {
457 assert!(Domain::General.contains_type_id(0));
458 assert!(Domain::General.contains_type_id(99));
459 assert!(!Domain::General.contains_type_id(100));
460
461 assert!(Domain::OrderMatching.contains_type_id(500));
462 assert!(Domain::OrderMatching.contains_type_id(599));
463 assert!(!Domain::OrderMatching.contains_type_id(600));
464
465 assert!(Domain::Custom.contains_type_id(10000));
466 assert!(Domain::Custom.contains_type_id(u64::MAX));
467 }
468
469 #[test]
470 fn test_domain_from_type_id() {
471 assert_eq!(Domain::from_type_id(0), Some(Domain::General));
472 assert_eq!(Domain::from_type_id(50), Some(Domain::General));
473 assert_eq!(Domain::from_type_id(99), Some(Domain::General));
474 assert_eq!(Domain::from_type_id(100), Some(Domain::GraphAnalytics));
475 assert_eq!(Domain::from_type_id(501), Some(Domain::OrderMatching));
476 assert_eq!(Domain::from_type_id(10500), Some(Domain::Custom));
477 assert_eq!(Domain::from_type_id(2500), None); }
479
480 #[test]
481 fn test_domain_from_str() {
482 assert_eq!(
484 Domain::from_str("OrderMatching"),
485 Some(Domain::OrderMatching)
486 );
487 assert_eq!(
488 Domain::from_str("RiskManagement"),
489 Some(Domain::RiskManagement)
490 );
491
492 assert_eq!(
494 Domain::from_str("order_matching"),
495 Some(Domain::OrderMatching)
496 );
497 assert_eq!(
498 Domain::from_str("risk_management"),
499 Some(Domain::RiskManagement)
500 );
501
502 assert_eq!(
504 Domain::from_str("ordermatching"),
505 Some(Domain::OrderMatching)
506 );
507
508 assert_eq!(Domain::from_str("risk"), Some(Domain::RiskManagement));
510 assert_eq!(Domain::from_str("ml"), Some(Domain::StatisticalML));
511 assert_eq!(Domain::from_str("sim"), Some(Domain::Simulation));
512
513 assert_eq!(Domain::from_str("unknown"), None);
515 assert_eq!(Domain::from_str(""), None);
516 }
517
518 #[test]
519 fn test_domain_as_str() {
520 assert_eq!(Domain::General.as_str(), "General");
521 assert_eq!(Domain::OrderMatching.as_str(), "OrderMatching");
522 assert_eq!(Domain::RiskManagement.as_str(), "RiskManagement");
523 }
524
525 #[test]
526 fn test_domain_display() {
527 assert_eq!(format!("{}", Domain::OrderMatching), "OrderMatching");
528 }
529
530 #[test]
531 fn test_domain_default() {
532 assert_eq!(Domain::default(), Domain::General);
533 }
534
535 #[test]
536 fn test_domain_all_standard() {
537 let all = Domain::all_standard();
538 assert_eq!(all.len(), 20);
539 assert!(all.contains(&Domain::General));
540 assert!(all.contains(&Domain::OrderMatching));
541 assert!(!all.contains(&Domain::Custom));
542 }
543
544 #[test]
545 fn test_domain_ranges_no_overlap() {
546 let domains = Domain::all_standard();
547 for (i, d1) in domains.iter().enumerate() {
548 for d2 in domains.iter().skip(i + 1) {
549 assert!(
551 d1.max_type_id() < d2.base_type_id() || d2.max_type_id() < d1.base_type_id(),
552 "Domains {:?} and {:?} have overlapping ranges",
553 d1,
554 d2
555 );
556 }
557 }
558 }
559
560 #[test]
561 fn test_std_from_str() {
562 let domain: Domain = "OrderMatching".parse().unwrap();
563 assert_eq!(domain, Domain::OrderMatching);
564
565 let err = "invalid".parse::<Domain>().unwrap_err();
566 assert!(err.to_string().contains("unknown domain"));
567 }
568}