nautilus_model/identifiers/
trader_id.rs1use std::fmt::{Debug, Display};
19
20use nautilus_core::correctness::{
21 CorrectnessResult, CorrectnessResultExt, FAILED, check_predicate_false, check_string_contains,
22 check_valid_string_ascii,
23};
24use ustr::Ustr;
25
26#[repr(C)]
28#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
29#[cfg_attr(
30 feature = "python",
31 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model", from_py_object)
32)]
33#[cfg_attr(
34 feature = "python",
35 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
36)]
37pub struct TraderId(Ustr);
38
39impl TraderId {
40 pub fn new_checked<T: AsRef<str>>(value: T) -> CorrectnessResult<Self> {
62 let value = value.as_ref();
63 check_valid_string_ascii(value, stringify!(value))?;
64 check_string_contains(value, "-", stringify!(value))?;
65
66 if let Some((name, tag)) = value.rsplit_once('-') {
67 check_predicate_false(
68 name.is_empty(),
69 "`value` name part (before '-') cannot be empty",
70 )?;
71 check_predicate_false(
72 tag.is_empty(),
73 "`value` tag part (after '-') cannot be empty",
74 )?;
75 }
76
77 Ok(Self(Ustr::from(value)))
78 }
79
80 pub fn new<T: AsRef<str>>(value: T) -> Self {
86 Self::new_checked(value).expect_display(FAILED)
87 }
88
89 #[cfg_attr(not(feature = "python"), allow(dead_code))]
91 pub(crate) fn set_inner(&mut self, value: &str) {
92 self.0 = Ustr::from(value);
93 }
94
95 #[must_use]
97 pub fn inner(&self) -> Ustr {
98 self.0
99 }
100
101 #[must_use]
103 pub fn as_str(&self) -> &str {
104 self.0.as_str()
105 }
106
107 #[must_use]
113 pub fn get_tag(&self) -> &str {
114 self.0.split('-').next_back().unwrap()
115 }
116
117 #[must_use]
119 pub fn external() -> Self {
120 Self::new("EXTERNAL-0")
121 }
122
123 #[must_use]
125 pub fn is_external(&self) -> bool {
126 self.0.as_str() == "EXTERNAL-0"
127 }
128}
129
130impl Default for TraderId {
131 fn default() -> Self {
133 Self::from("TRADER-001")
134 }
135}
136
137impl Debug for TraderId {
138 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139 write!(f, "\"{}\"", self.0)
140 }
141}
142
143impl Display for TraderId {
144 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145 write!(f, "{}", self.0)
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use nautilus_core::correctness::CorrectnessError;
152 use rstest::rstest;
153
154 use crate::identifiers::{stubs::*, trader_id::TraderId};
155
156 #[rstest]
157 fn test_string_reprs(trader_id: TraderId) {
158 assert_eq!(trader_id.as_str(), "TRADER-001");
159 assert_eq!(format!("{trader_id}"), "TRADER-001");
160 }
161
162 #[rstest]
163 fn test_get_tag(trader_id: TraderId) {
164 assert_eq!(trader_id.get_tag(), "001");
165 }
166
167 #[rstest]
168 #[should_panic(expected = "name part (before '-') cannot be empty")]
169 fn test_new_with_empty_name_panics() {
170 let _ = TraderId::new("-001");
171 }
172
173 #[rstest]
174 #[should_panic(expected = "tag part (after '-') cannot be empty")]
175 fn test_new_with_empty_tag_panics() {
176 let _ = TraderId::new("TRADER-");
177 }
178
179 #[rstest]
180 fn test_new_checked_with_empty_name_returns_error() {
181 assert!(TraderId::new_checked("-001").is_err());
182 }
183
184 #[rstest]
185 fn test_new_checked_with_empty_tag_returns_error() {
186 assert!(TraderId::new_checked("TRADER-").is_err());
187 }
188
189 #[rstest]
190 fn test_new_checked_with_empty_name_returns_typed_error_with_stable_display() {
191 let error = TraderId::new_checked("-001").unwrap_err();
192
193 match error {
194 CorrectnessError::PredicateViolation { ref message } => {
195 assert_eq!(message, "`value` name part (before '-') cannot be empty");
196 }
197 other => panic!("Expected typed predicate violation, was: {other:?}"),
198 }
199
200 assert_eq!(
201 error.to_string(),
202 "`value` name part (before '-') cannot be empty"
203 );
204 }
205
206 #[rstest]
207 fn test_new_checked_with_empty_tag_returns_typed_error_with_stable_display() {
208 let error = TraderId::new_checked("TRADER-").unwrap_err();
209
210 match error {
211 CorrectnessError::PredicateViolation { ref message } => {
212 assert_eq!(message, "`value` tag part (after '-') cannot be empty");
213 }
214 other => panic!("Expected typed predicate violation, was: {other:?}"),
215 }
216
217 assert_eq!(
218 error.to_string(),
219 "`value` tag part (after '-') cannot be empty"
220 );
221 }
222}