nautilus_model/identifiers/
account_id.rs1use std::{
19 fmt::{Debug, Display},
20 hash::Hash,
21};
22
23use nautilus_core::correctness::{
24 CorrectnessResult, CorrectnessResultExt, FAILED, check_predicate_false, check_string_contains,
25 check_valid_string_ascii,
26};
27use ustr::Ustr;
28
29use super::Venue;
30
31#[repr(C)]
33#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
34#[cfg_attr(
35 feature = "python",
36 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model", from_py_object)
37)]
38#[cfg_attr(
39 feature = "python",
40 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
41)]
42pub struct AccountId(Ustr);
43
44impl AccountId {
45 pub fn new_checked<T: AsRef<str>>(value: T) -> CorrectnessResult<Self> {
63 let value = value.as_ref();
64 check_valid_string_ascii(value, stringify!(value))?;
65 check_string_contains(value, "-", stringify!(value))?;
66
67 if let Some((issuer, account)) = value.split_once('-') {
68 check_predicate_false(
69 issuer.is_empty(),
70 "`value` issuer part (before '-') cannot be empty",
71 )?;
72 check_predicate_false(
73 account.is_empty(),
74 "`value` account part (after '-') cannot be empty",
75 )?;
76 }
77
78 Ok(Self(Ustr::from(value)))
79 }
80
81 pub fn new<T: AsRef<str>>(value: T) -> Self {
87 Self::new_checked(value).expect_display(FAILED)
88 }
89
90 #[cfg_attr(not(feature = "python"), allow(dead_code))]
92 pub(crate) fn set_inner(&mut self, value: &str) {
93 self.0 = Ustr::from(value);
94 }
95
96 #[must_use]
98 pub fn inner(&self) -> Ustr {
99 self.0
100 }
101
102 #[must_use]
104 pub fn as_str(&self) -> &str {
105 self.0.as_str()
106 }
107
108 #[must_use]
114 pub fn get_issuer(&self) -> Venue {
115 Venue::from_str_unchecked(self.0.split_once('-').expect("AccountId contains '-'").0)
116 }
117
118 #[must_use]
124 pub fn get_issuers_id(&self) -> &str {
125 self.0.split_once('-').expect("AccountId contains '-'").1
126 }
127}
128
129impl Debug for AccountId {
130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131 write!(f, "\"{}\"", self.0)
132 }
133}
134
135impl Display for AccountId {
136 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137 write!(f, "{}", self.0)
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use nautilus_core::correctness::CorrectnessError;
144 use rstest::rstest;
145
146 use super::*;
147 use crate::identifiers::stubs::*;
148
149 #[rstest]
150 #[should_panic(expected = "invalid string for 'value', was empty")]
151 fn test_account_id_new_invalid_string() {
152 AccountId::new("");
153 }
154
155 #[rstest]
156 #[should_panic(expected = "did not contain '-'")]
157 fn test_account_id_new_missing_hyphen() {
158 AccountId::new("123456789");
159 }
160
161 #[rstest]
162 fn test_account_id_fmt() {
163 let s = "IB-U123456789";
164 let account_id = AccountId::new(s);
165 let formatted = format!("{account_id}");
166 assert_eq!(formatted, s);
167 }
168
169 #[rstest]
170 fn test_string_reprs(account_ib: AccountId) {
171 assert_eq!(account_ib.as_str(), "IB-1234567890");
172 }
173
174 #[rstest]
175 fn test_get_issuer(account_ib: AccountId) {
176 assert_eq!(account_ib.get_issuer(), Venue::new("IB"));
177 }
178
179 #[rstest]
180 fn test_get_issuers_id(account_ib: AccountId) {
181 assert_eq!(account_ib.get_issuers_id(), "1234567890");
182 }
183
184 #[rstest]
185 #[should_panic(expected = "issuer part (before '-') cannot be empty")]
186 fn test_new_with_empty_issuer_panics() {
187 let _ = AccountId::new("-123456");
188 }
189
190 #[rstest]
191 #[should_panic(expected = "account part (after '-') cannot be empty")]
192 fn test_new_with_empty_account_panics() {
193 let _ = AccountId::new("IB-");
194 }
195
196 #[rstest]
197 fn test_new_checked_with_empty_issuer_returns_error() {
198 assert!(AccountId::new_checked("-123456").is_err());
199 }
200
201 #[rstest]
202 fn test_new_checked_with_empty_account_returns_error() {
203 assert!(AccountId::new_checked("IB-").is_err());
204 }
205
206 #[rstest]
207 fn test_new_checked_with_empty_issuer_returns_typed_error_with_stable_display() {
208 let error = AccountId::new_checked("-123456").unwrap_err();
209
210 match error {
211 CorrectnessError::PredicateViolation { ref message } => {
212 assert_eq!(message, "`value` issuer part (before '-') cannot be empty");
213 }
214 other => panic!("Expected typed predicate violation, was: {other:?}"),
215 }
216
217 assert_eq!(
218 error.to_string(),
219 "`value` issuer part (before '-') cannot be empty"
220 );
221 }
222
223 #[rstest]
224 fn test_new_checked_with_empty_account_returns_typed_error_with_stable_display() {
225 let error = AccountId::new_checked("IB-").unwrap_err();
226
227 match error {
228 CorrectnessError::PredicateViolation { ref message } => {
229 assert_eq!(message, "`value` account part (after '-') cannot be empty");
230 }
231 other => panic!("Expected typed predicate violation, was: {other:?}"),
232 }
233
234 assert_eq!(
235 error.to_string(),
236 "`value` account part (after '-') cannot be empty"
237 );
238 }
239}