use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[repr(u16)]
#[non_exhaustive]
pub enum Domain {
#[default]
General = 0,
GraphAnalytics = 1,
StatisticalML = 2,
Compliance = 3,
RiskManagement = 4,
OrderMatching = 5,
MarketData = 6,
Settlement = 7,
Accounting = 8,
NetworkAnalysis = 9,
FraudDetection = 10,
TimeSeries = 11,
Simulation = 12,
Banking = 13,
BehavioralAnalytics = 14,
ProcessIntelligence = 15,
Clearing = 16,
TreasuryManagement = 17,
PaymentProcessing = 18,
FinancialAudit = 19,
Custom = 100,
}
impl Domain {
pub const RANGE_SIZE: u64 = 100;
pub const CUSTOM_BASE: u64 = 10000;
#[inline]
pub const fn base_type_id(&self) -> u64 {
match self {
Self::General => 0,
Self::GraphAnalytics => 100,
Self::StatisticalML => 200,
Self::Compliance => 300,
Self::RiskManagement => 400,
Self::OrderMatching => 500,
Self::MarketData => 600,
Self::Settlement => 700,
Self::Accounting => 800,
Self::NetworkAnalysis => 900,
Self::FraudDetection => 1000,
Self::TimeSeries => 1100,
Self::Simulation => 1200,
Self::Banking => 1300,
Self::BehavioralAnalytics => 1400,
Self::ProcessIntelligence => 1500,
Self::Clearing => 1600,
Self::TreasuryManagement => 1700,
Self::PaymentProcessing => 1800,
Self::FinancialAudit => 1900,
Self::Custom => Self::CUSTOM_BASE,
}
}
#[inline]
pub const fn max_type_id(&self) -> u64 {
match self {
Self::Custom => u64::MAX,
_ => self.base_type_id() + Self::RANGE_SIZE - 1,
}
}
#[inline]
pub const fn contains_type_id(&self, type_id: u64) -> bool {
type_id >= self.base_type_id() && type_id <= self.max_type_id()
}
pub const fn from_type_id(type_id: u64) -> Option<Self> {
match type_id {
0..=99 => Some(Self::General),
100..=199 => Some(Self::GraphAnalytics),
200..=299 => Some(Self::StatisticalML),
300..=399 => Some(Self::Compliance),
400..=499 => Some(Self::RiskManagement),
500..=599 => Some(Self::OrderMatching),
600..=699 => Some(Self::MarketData),
700..=799 => Some(Self::Settlement),
800..=899 => Some(Self::Accounting),
900..=999 => Some(Self::NetworkAnalysis),
1000..=1099 => Some(Self::FraudDetection),
1100..=1199 => Some(Self::TimeSeries),
1200..=1299 => Some(Self::Simulation),
1300..=1399 => Some(Self::Banking),
1400..=1499 => Some(Self::BehavioralAnalytics),
1500..=1599 => Some(Self::ProcessIntelligence),
1600..=1699 => Some(Self::Clearing),
1700..=1799 => Some(Self::TreasuryManagement),
1800..=1899 => Some(Self::PaymentProcessing),
1900..=1999 => Some(Self::FinancialAudit),
10000.. => Some(Self::Custom),
_ => None,
}
}
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &str) -> Option<Self> {
let normalized: String = s
.chars()
.filter(|c| c.is_alphanumeric())
.collect::<String>()
.to_lowercase();
match normalized.as_str() {
"general" | "gen" => Some(Self::General),
"graphanalytics" | "graph" => Some(Self::GraphAnalytics),
"statisticalml" | "ml" | "machinelearning" => Some(Self::StatisticalML),
"compliance" | "comp" | "regulatory" => Some(Self::Compliance),
"riskmanagement" | "risk" => Some(Self::RiskManagement),
"ordermatching" | "orders" | "order" | "matching" => Some(Self::OrderMatching),
"marketdata" | "market" | "mktdata" => Some(Self::MarketData),
"settlement" | "settle" => Some(Self::Settlement),
"accounting" | "acct" | "ledger" => Some(Self::Accounting),
"networkanalysis" | "network" | "netanalysis" => Some(Self::NetworkAnalysis),
"frauddetection" | "fraud" | "aml" => Some(Self::FraudDetection),
"timeseries" | "ts" | "temporal" => Some(Self::TimeSeries),
"simulation" | "sim" | "montecarlo" => Some(Self::Simulation),
"banking" | "bank" => Some(Self::Banking),
"behavioralanalytics" | "behavioral" | "behavior" => Some(Self::BehavioralAnalytics),
"processintelligence" | "process" | "processmining" => Some(Self::ProcessIntelligence),
"clearing" | "ccp" => Some(Self::Clearing),
"treasurymanagement" | "treasury" => Some(Self::TreasuryManagement),
"paymentprocessing" | "payment" | "payments" => Some(Self::PaymentProcessing),
"financialaudit" | "audit" => Some(Self::FinancialAudit),
"custom" => Some(Self::Custom),
_ => None,
}
}
#[inline]
pub const fn as_str(&self) -> &'static str {
match self {
Self::General => "General",
Self::GraphAnalytics => "GraphAnalytics",
Self::StatisticalML => "StatisticalML",
Self::Compliance => "Compliance",
Self::RiskManagement => "RiskManagement",
Self::OrderMatching => "OrderMatching",
Self::MarketData => "MarketData",
Self::Settlement => "Settlement",
Self::Accounting => "Accounting",
Self::NetworkAnalysis => "NetworkAnalysis",
Self::FraudDetection => "FraudDetection",
Self::TimeSeries => "TimeSeries",
Self::Simulation => "Simulation",
Self::Banking => "Banking",
Self::BehavioralAnalytics => "BehavioralAnalytics",
Self::ProcessIntelligence => "ProcessIntelligence",
Self::Clearing => "Clearing",
Self::TreasuryManagement => "TreasuryManagement",
Self::PaymentProcessing => "PaymentProcessing",
Self::FinancialAudit => "FinancialAudit",
Self::Custom => "Custom",
}
}
pub const fn all_standard() -> &'static [Domain] {
&[
Self::General,
Self::GraphAnalytics,
Self::StatisticalML,
Self::Compliance,
Self::RiskManagement,
Self::OrderMatching,
Self::MarketData,
Self::Settlement,
Self::Accounting,
Self::NetworkAnalysis,
Self::FraudDetection,
Self::TimeSeries,
Self::Simulation,
Self::Banking,
Self::BehavioralAnalytics,
Self::ProcessIntelligence,
Self::Clearing,
Self::TreasuryManagement,
Self::PaymentProcessing,
Self::FinancialAudit,
]
}
}
impl fmt::Display for Domain {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl std::str::FromStr for Domain {
type Err = DomainParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Domain::from_str(s).ok_or_else(|| DomainParseError(s.to_string()))
}
}
#[derive(Debug, Clone)]
pub struct DomainParseError(pub String);
impl fmt::Display for DomainParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "unknown domain: '{}'", self.0)
}
}
impl std::error::Error for DomainParseError {}
pub trait DomainMessage: crate::message::RingMessage {
fn domain() -> Domain;
fn domain_type_id() -> u64 {
Self::message_type().saturating_sub(Self::domain().base_type_id())
}
fn is_valid_domain_type_id() -> bool {
Self::domain().contains_type_id(Self::message_type())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_domain_base_type_ids() {
assert_eq!(Domain::General.base_type_id(), 0);
assert_eq!(Domain::GraphAnalytics.base_type_id(), 100);
assert_eq!(Domain::StatisticalML.base_type_id(), 200);
assert_eq!(Domain::OrderMatching.base_type_id(), 500);
assert_eq!(Domain::Custom.base_type_id(), 10000);
}
#[test]
fn test_domain_max_type_ids() {
assert_eq!(Domain::General.max_type_id(), 99);
assert_eq!(Domain::OrderMatching.max_type_id(), 599);
assert_eq!(Domain::Custom.max_type_id(), u64::MAX);
}
#[test]
fn test_domain_contains_type_id() {
assert!(Domain::General.contains_type_id(0));
assert!(Domain::General.contains_type_id(99));
assert!(!Domain::General.contains_type_id(100));
assert!(Domain::OrderMatching.contains_type_id(500));
assert!(Domain::OrderMatching.contains_type_id(599));
assert!(!Domain::OrderMatching.contains_type_id(600));
assert!(Domain::Custom.contains_type_id(10000));
assert!(Domain::Custom.contains_type_id(u64::MAX));
}
#[test]
fn test_domain_from_type_id() {
assert_eq!(Domain::from_type_id(0), Some(Domain::General));
assert_eq!(Domain::from_type_id(50), Some(Domain::General));
assert_eq!(Domain::from_type_id(99), Some(Domain::General));
assert_eq!(Domain::from_type_id(100), Some(Domain::GraphAnalytics));
assert_eq!(Domain::from_type_id(501), Some(Domain::OrderMatching));
assert_eq!(Domain::from_type_id(10500), Some(Domain::Custom));
assert_eq!(Domain::from_type_id(2500), None); }
#[test]
fn test_domain_from_str() {
assert_eq!(
Domain::from_str("OrderMatching"),
Some(Domain::OrderMatching)
);
assert_eq!(
Domain::from_str("RiskManagement"),
Some(Domain::RiskManagement)
);
assert_eq!(
Domain::from_str("order_matching"),
Some(Domain::OrderMatching)
);
assert_eq!(
Domain::from_str("risk_management"),
Some(Domain::RiskManagement)
);
assert_eq!(
Domain::from_str("ordermatching"),
Some(Domain::OrderMatching)
);
assert_eq!(Domain::from_str("risk"), Some(Domain::RiskManagement));
assert_eq!(Domain::from_str("ml"), Some(Domain::StatisticalML));
assert_eq!(Domain::from_str("sim"), Some(Domain::Simulation));
assert_eq!(Domain::from_str("unknown"), None);
assert_eq!(Domain::from_str(""), None);
}
#[test]
fn test_domain_as_str() {
assert_eq!(Domain::General.as_str(), "General");
assert_eq!(Domain::OrderMatching.as_str(), "OrderMatching");
assert_eq!(Domain::RiskManagement.as_str(), "RiskManagement");
}
#[test]
fn test_domain_display() {
assert_eq!(format!("{}", Domain::OrderMatching), "OrderMatching");
}
#[test]
fn test_domain_default() {
assert_eq!(Domain::default(), Domain::General);
}
#[test]
fn test_domain_all_standard() {
let all = Domain::all_standard();
assert_eq!(all.len(), 20);
assert!(all.contains(&Domain::General));
assert!(all.contains(&Domain::OrderMatching));
assert!(!all.contains(&Domain::Custom));
}
#[test]
fn test_domain_ranges_no_overlap() {
let domains = Domain::all_standard();
for (i, d1) in domains.iter().enumerate() {
for d2 in domains.iter().skip(i + 1) {
assert!(
d1.max_type_id() < d2.base_type_id() || d2.max_type_id() < d1.base_type_id(),
"Domains {:?} and {:?} have overlapping ranges",
d1,
d2
);
}
}
}
#[test]
fn test_std_from_str() {
let domain: Domain = "OrderMatching".parse().unwrap();
assert_eq!(domain, Domain::OrderMatching);
let err = "invalid".parse::<Domain>().unwrap_err();
assert!(err.to_string().contains("unknown domain"));
}
}