nautilus_model/identifiers/
trade_id.rs1use std::{
19 ffi::CStr,
20 fmt::{Debug, Display},
21 hash::Hash,
22};
23
24use nautilus_core::{StackStr, correctness::CorrectnessResult};
25use serde::{Deserialize, Deserializer, Serialize, Serializer};
26
27#[repr(C)]
36#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
37#[cfg_attr(
38 feature = "python",
39 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model", from_py_object)
40)]
41#[cfg_attr(
42 feature = "python",
43 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
44)]
45pub struct TradeId(StackStr);
46
47impl TradeId {
48 pub fn new_checked<T: AsRef<str>>(value: T) -> CorrectnessResult<Self> {
62 Ok(Self(StackStr::new_checked(value.as_ref())?))
63 }
64
65 pub fn new<T: AsRef<str>>(value: T) -> Self {
75 Self(StackStr::new(value.as_ref()))
76 }
77
78 pub fn from_bytes(bytes: &[u8]) -> CorrectnessResult<Self> {
85 Ok(Self(StackStr::from_bytes(bytes)?))
86 }
87
88 #[inline]
90 #[must_use]
91 pub fn as_str(&self) -> &str {
92 self.0.as_str()
93 }
94
95 #[inline]
97 #[must_use]
98 pub fn as_cstr(&self) -> &CStr {
99 self.0.as_cstr()
100 }
101}
102
103impl Debug for TradeId {
104 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105 write!(f, "{}('{}')", stringify!(TradeId), self)
106 }
107}
108
109impl Display for TradeId {
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 write!(f, "{}", self.0)
112 }
113}
114
115impl Serialize for TradeId {
116 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
117 where
118 S: Serializer,
119 {
120 self.0.serialize(serializer)
121 }
122}
123
124impl<'de> Deserialize<'de> for TradeId {
125 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
126 where
127 D: Deserializer<'de>,
128 {
129 let inner = StackStr::deserialize(deserializer)?;
130 Ok(Self(inner))
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use nautilus_core::correctness::CorrectnessError;
137 use rstest::rstest;
138
139 use crate::identifiers::{TradeId, stubs::*};
140
141 #[rstest]
142 fn test_trade_id_new_valid() {
143 let trade_id = TradeId::new("TRADE12345");
144 assert_eq!(trade_id.to_string(), "TRADE12345");
145 }
146
147 #[rstest]
148 fn test_trade_id_new_checked_returns_typed_error_with_stable_display() {
149 let error = TradeId::new_checked("").unwrap_err();
150
151 assert_eq!(
152 error,
153 CorrectnessError::PredicateViolation {
154 message: "String is empty".to_string(),
155 }
156 );
157 assert_eq!(error.to_string(), "String is empty");
158 }
159
160 #[rstest]
161 #[should_panic(expected = "exceeds maximum length")]
162 fn test_trade_id_new_invalid_length() {
163 let _ = TradeId::new("A".repeat(37).as_str());
164 }
165
166 #[rstest]
167 #[case(b"1234567890", "1234567890")]
168 #[case(b"ABCDEFGHIJKLMNOPQRSTUVWXYZ1234", "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234")] #[case(b"1234567890\0", "1234567890")]
170 #[case(b"ABCDEFGHIJKLMNOPQRSTUVWXYZ1234\0", "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234")] fn test_trade_id_from_valid_bytes(#[case] input: &[u8], #[case] expected: &str) {
172 let trade_id = TradeId::from_bytes(input).unwrap();
173 assert_eq!(trade_id.to_string(), expected);
174 }
175
176 #[rstest]
177 #[should_panic(expected = "String is empty")]
178 fn test_trade_id_from_bytes_empty() {
179 TradeId::from_bytes(&[] as &[u8]).unwrap();
180 }
181
182 #[rstest]
183 #[should_panic(expected = "String is empty")]
184 fn test_trade_id_single_null_byte() {
185 TradeId::from_bytes(&[0u8] as &[u8]).unwrap();
186 }
187
188 #[rstest]
189 #[case(b"ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901")] #[case(b"ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901\0")] #[should_panic(expected = "exceeds maximum length")]
192 fn test_trade_id_exceeds_max_length(#[case] input: &[u8]) {
193 TradeId::from_bytes(input).unwrap();
194 }
195
196 #[rstest]
197 fn test_trade_id_with_null_terminator_at_max_length() {
198 let input = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\0" as &[u8];
199 let trade_id = TradeId::from_bytes(input).unwrap();
200 assert_eq!(trade_id.to_string(), "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); }
202
203 #[rstest]
204 fn test_trade_id_as_cstr() {
205 let trade_id = TradeId::new("TRADE12345");
206 assert_eq!(trade_id.as_cstr().to_str().unwrap(), "TRADE12345");
207 }
208
209 #[rstest]
210 fn test_trade_id_as_str() {
211 let trade_id = TradeId::new("TRADE12345");
212 assert_eq!(trade_id.as_str(), "TRADE12345");
213 }
214
215 #[rstest]
216 fn test_trade_id_equality() {
217 let trade_id1 = TradeId::new("TRADE12345");
218 let trade_id2 = TradeId::new("TRADE12345");
219 assert_eq!(trade_id1, trade_id2);
220 }
221
222 #[rstest]
223 fn test_string_reprs(trade_id: TradeId) {
224 assert_eq!(trade_id.to_string(), "1234567890");
225 assert_eq!(format!("{trade_id}"), "1234567890");
226 assert_eq!(format!("{trade_id:?}"), "TradeId('1234567890')");
227 }
228
229 #[rstest]
230 fn test_trade_id_ordering() {
231 let trade_id1 = TradeId::new("TRADE12345");
232 let trade_id2 = TradeId::new("TRADE12346");
233 assert!(trade_id1 < trade_id2);
234 }
235
236 #[rstest]
237 fn test_trade_id_serialization() {
238 let trade_id = TradeId::new("TRADE12345");
239 let json = serde_json::to_string(&trade_id).unwrap();
240 assert_eq!(json, "\"TRADE12345\"");
241
242 let deserialized: TradeId = serde_json::from_str(&json).unwrap();
243 assert_eq!(trade_id, deserialized);
244 }
245}