nautilus_model/identifiers/
account_id.rs1use std::{
19 fmt::{Debug, Display},
20 hash::Hash,
21};
22
23use nautilus_core::correctness::{FAILED, check_string_contains, check_valid_string_ascii};
24use ustr::Ustr;
25
26use super::Venue;
27
28#[repr(C)]
30#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
31#[cfg_attr(
32 feature = "python",
33 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
34)]
35pub struct AccountId(Ustr);
36
37impl AccountId {
38 pub fn new_checked<T: AsRef<str>>(value: T) -> anyhow::Result<Self> {
56 let value = value.as_ref();
57 check_valid_string_ascii(value, stringify!(value))?;
58 check_string_contains(value, "-", stringify!(value))?;
59
60 if let Some((issuer, account)) = value.split_once('-') {
61 anyhow::ensure!(
62 !issuer.is_empty(),
63 "`value` issuer part (before '-') cannot be empty"
64 );
65 anyhow::ensure!(
66 !account.is_empty(),
67 "`value` account part (after '-') cannot be empty"
68 );
69 }
70
71 Ok(Self(Ustr::from(value)))
72 }
73
74 pub fn new<T: AsRef<str>>(value: T) -> Self {
80 Self::new_checked(value).expect(FAILED)
81 }
82
83 #[cfg_attr(not(feature = "python"), allow(dead_code))]
85 pub(crate) fn set_inner(&mut self, value: &str) {
86 self.0 = Ustr::from(value);
87 }
88
89 #[must_use]
91 pub fn inner(&self) -> Ustr {
92 self.0
93 }
94
95 #[must_use]
97 pub fn as_str(&self) -> &str {
98 self.0.as_str()
99 }
100
101 #[must_use]
107 pub fn get_issuer(&self) -> Venue {
108 Venue::from_str_unchecked(self.0.split_once('-').unwrap().0)
110 }
111
112 #[must_use]
118 pub fn get_issuers_id(&self) -> &str {
119 self.0.split_once('-').unwrap().1
121 }
122}
123
124impl Debug for AccountId {
125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 write!(f, "\"{}\"", self.0)
127 }
128}
129
130impl Display for AccountId {
131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132 write!(f, "{}", self.0)
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use rstest::rstest;
139
140 use super::*;
141 use crate::identifiers::stubs::*;
142
143 #[rstest]
144 #[should_panic]
145 fn test_account_id_new_invalid_string() {
146 AccountId::new("");
147 }
148
149 #[rstest]
150 #[should_panic]
151 fn test_account_id_new_missing_hyphen() {
152 AccountId::new("123456789");
153 }
154
155 #[rstest]
156 fn test_account_id_fmt() {
157 let s = "IB-U123456789";
158 let account_id = AccountId::new(s);
159 let formatted = format!("{account_id}");
160 assert_eq!(formatted, s);
161 }
162
163 #[rstest]
164 fn test_string_reprs(account_ib: AccountId) {
165 assert_eq!(account_ib.as_str(), "IB-1234567890");
166 }
167
168 #[rstest]
169 fn test_get_issuer(account_ib: AccountId) {
170 assert_eq!(account_ib.get_issuer(), Venue::new("IB"));
171 }
172
173 #[rstest]
174 fn test_get_issuers_id(account_ib: AccountId) {
175 assert_eq!(account_ib.get_issuers_id(), "1234567890");
176 }
177
178 #[rstest]
179 #[should_panic(expected = "issuer part (before '-') cannot be empty")]
180 fn test_new_with_empty_issuer_panics() {
181 let _ = AccountId::new("-123456");
182 }
183
184 #[rstest]
185 #[should_panic(expected = "account part (after '-') cannot be empty")]
186 fn test_new_with_empty_account_panics() {
187 let _ = AccountId::new("IB-");
188 }
189
190 #[rstest]
191 fn test_new_checked_with_empty_issuer_returns_error() {
192 assert!(AccountId::new_checked("-123456").is_err());
193 }
194
195 #[rstest]
196 fn test_new_checked_with_empty_account_returns_error() {
197 assert!(AccountId::new_checked("IB-").is_err());
198 }
199}