rusty_model/
types.rs

1//! Newtype wrappers for type safety in the trading system
2//!
3//! This module provides strongly-typed wrappers for common identifiers
4//! to prevent mixing different ID types and improve code clarity.
5
6use rusty_common::SmartString;
7use serde::{Deserialize, Serialize};
8use std::{fmt, str::FromStr};
9use uuid::Uuid;
10
11/// Unique identifier for an order
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13#[serde(transparent)]
14pub struct OrderId(pub Uuid);
15
16impl OrderId {
17    /// Create a new random order ID
18    #[inline]
19    #[must_use]
20    #[allow(clippy::new_without_default)] // Default implementation removed for security
21    pub fn new() -> Self {
22        Self(Uuid::new_v4())
23    }
24
25    /// Create an order ID from an existing UUID
26    #[inline]
27    #[must_use]
28    pub const fn from_uuid(id: Uuid) -> Self {
29        Self(id)
30    }
31
32    /// Get the underlying UUID
33    #[inline]
34    #[must_use]
35    pub const fn as_uuid(&self) -> &Uuid {
36        &self.0
37    }
38
39    /// Convert to the underlying UUID
40    #[inline]
41    #[must_use]
42    pub const fn into_uuid(self) -> Uuid {
43        self.0
44    }
45}
46
47// SAFETY: Default implementation removed to prevent accidental random ID generation.
48// Use OrderId::new() for production or OrderId::for_testing() for tests.
49
50#[cfg(test)]
51impl OrderId {
52    /// Create a test OrderId with nil UUID (all zeros)
53    ///
54    /// # Safety
55    /// This creates an OrderId with a nil UUID (00000000-0000-0000-0000-000000000000)
56    /// which is obviously invalid and should never be used in production.
57    ///
58    /// # Examples
59    /// ```rust
60    /// # use rusty_model::types::OrderId;
61    /// let test_id = OrderId::for_testing();
62    /// assert_eq!(test_id.to_string(), "00000000-0000-0000-0000-000000000000");
63    /// ```
64    #[must_use]
65    pub fn for_testing() -> Self {
66        Self(Uuid::nil())
67    }
68}
69
70impl fmt::Display for OrderId {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        write!(f, "{}", self.0)
73    }
74}
75
76impl From<Uuid> for OrderId {
77    fn from(id: Uuid) -> Self {
78        Self(id)
79    }
80}
81
82impl From<OrderId> for Uuid {
83    fn from(id: OrderId) -> Self {
84        id.0
85    }
86}
87
88/// Unique identifier for a trade
89#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
90#[serde(transparent)]
91pub struct TradeId(pub Uuid);
92
93impl TradeId {
94    /// Create a new random trade ID
95    #[inline]
96    #[must_use]
97    #[allow(clippy::new_without_default)] // Default implementation removed for security
98    pub fn new() -> Self {
99        Self(Uuid::new_v4())
100    }
101
102    /// Create a trade ID from an existing UUID
103    #[inline]
104    #[must_use]
105    pub const fn from_uuid(id: Uuid) -> Self {
106        Self(id)
107    }
108
109    /// Get the underlying UUID
110    #[inline]
111    #[must_use]
112    pub const fn as_uuid(&self) -> &Uuid {
113        &self.0
114    }
115
116    /// Convert to the underlying UUID
117    #[inline]
118    #[must_use]
119    pub const fn into_uuid(self) -> Uuid {
120        self.0
121    }
122}
123
124// SAFETY: Default implementation removed to prevent accidental random ID generation.
125// Use TradeId::new() for production or TradeId::for_testing() for tests.
126
127#[cfg(test)]
128impl TradeId {
129    /// Create a test TradeId with nil UUID (all zeros)
130    ///
131    /// # Safety
132    /// This creates a TradeId with a nil UUID (00000000-0000-0000-0000-000000000000)
133    /// which is obviously invalid and should never be used in production.
134    ///
135    /// # Examples
136    /// ```rust
137    /// # use rusty_model::types::TradeId;
138    /// let test_id = TradeId::for_testing();
139    /// assert_eq!(test_id.to_string(), "00000000-0000-0000-0000-000000000000");
140    /// ```
141    #[must_use]
142    pub fn for_testing() -> Self {
143        Self(Uuid::nil())
144    }
145}
146
147impl fmt::Display for TradeId {
148    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149        write!(f, "{}", self.0)
150    }
151}
152
153impl From<Uuid> for TradeId {
154    fn from(id: Uuid) -> Self {
155        Self(id)
156    }
157}
158
159impl From<TradeId> for Uuid {
160    fn from(id: TradeId) -> Self {
161        id.0
162    }
163}
164
165/// Unique identifier for a position
166#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
167#[serde(transparent)]
168pub struct PositionId(pub Uuid);
169
170impl PositionId {
171    /// Create a new random position ID
172    #[inline]
173    #[must_use]
174    #[allow(clippy::new_without_default)] // Default implementation removed for security
175    pub fn new() -> Self {
176        Self(Uuid::new_v4())
177    }
178
179    /// Create a position ID from an existing UUID
180    #[inline]
181    #[must_use]
182    pub const fn from_uuid(id: Uuid) -> Self {
183        Self(id)
184    }
185
186    /// Get the underlying UUID
187    #[inline]
188    #[must_use]
189    pub const fn as_uuid(&self) -> &Uuid {
190        &self.0
191    }
192
193    /// Convert to the underlying UUID
194    #[inline]
195    #[must_use]
196    pub const fn into_uuid(self) -> Uuid {
197        self.0
198    }
199}
200
201// SAFETY: Default implementation removed to prevent accidental random ID generation.
202// Use PositionId::new() for production or PositionId::for_testing() for tests.
203
204#[cfg(test)]
205impl PositionId {
206    /// Create a test PositionId with nil UUID (all zeros)
207    ///
208    /// # Safety
209    /// This creates a PositionId with a nil UUID (00000000-0000-0000-0000-000000000000)
210    /// which is obviously invalid and should never be used in production.
211    ///
212    /// # Examples
213    /// ```rust
214    /// # use rusty_model::types::PositionId;
215    /// let test_id = PositionId::for_testing();
216    /// assert_eq!(test_id.to_string(), "00000000-0000-0000-0000-000000000000");
217    /// ```
218    #[must_use]
219    pub fn for_testing() -> Self {
220        Self(Uuid::nil())
221    }
222}
223
224impl fmt::Display for PositionId {
225    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226        write!(f, "{}", self.0)
227    }
228}
229
230impl From<Uuid> for PositionId {
231    fn from(id: Uuid) -> Self {
232        Self(id)
233    }
234}
235
236impl From<PositionId> for Uuid {
237    fn from(id: PositionId) -> Self {
238        id.0
239    }
240}
241
242/// Unique identifier for a strategy
243#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
244pub struct StrategyId(pub SmartString);
245
246impl StrategyId {
247    /// Create a new strategy ID
248    #[inline]
249    #[must_use]
250    pub fn new(id: impl AsRef<str>) -> Self {
251        Self(id.as_ref().into())
252    }
253
254    /// Create a strategy ID from a string slice
255    #[inline]
256    #[must_use]
257    pub fn from_string(id: &str) -> Self {
258        Self(id.into())
259    }
260
261    /// Get the underlying string
262    #[inline]
263    #[must_use]
264    pub fn as_str(&self) -> &str {
265        self.0.as_str()
266    }
267
268    /// Convert to the underlying `SmartString`
269    #[inline]
270    #[must_use]
271    pub fn into_smart_string(self) -> SmartString {
272        self.0
273    }
274}
275
276impl fmt::Display for StrategyId {
277    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
278        write!(f, "{}", self.0)
279    }
280}
281
282impl From<SmartString> for StrategyId {
283    fn from(id: SmartString) -> Self {
284        Self(id)
285    }
286}
287
288impl From<&str> for StrategyId {
289    fn from(id: &str) -> Self {
290        Self(id.into())
291    }
292}
293
294impl From<StrategyId> for SmartString {
295    fn from(id: StrategyId) -> Self {
296        id.0
297    }
298}
299
300impl FromStr for StrategyId {
301    type Err = std::convert::Infallible;
302
303    fn from_str(s: &str) -> Result<Self, Self::Err> {
304        Ok(Self(s.into()))
305    }
306}
307
308/// Unique identifier for a client
309#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
310pub struct ClientId(pub SmartString);
311
312impl ClientId {
313    /// Create a new client ID
314    #[inline]
315    #[must_use]
316    pub fn new(id: impl AsRef<str>) -> Self {
317        Self(id.as_ref().into())
318    }
319
320    /// Create a client ID from a string slice
321    #[inline]
322    #[must_use]
323    pub fn from_string(id: &str) -> Self {
324        Self(id.into())
325    }
326
327    /// Get the underlying string
328    #[inline]
329    #[must_use]
330    pub fn as_str(&self) -> &str {
331        self.0.as_str()
332    }
333
334    /// Convert to the underlying `SmartString`
335    #[inline]
336    #[must_use]
337    pub fn into_smart_string(self) -> SmartString {
338        self.0
339    }
340}
341
342impl fmt::Display for ClientId {
343    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
344        write!(f, "{}", self.0)
345    }
346}
347
348impl From<SmartString> for ClientId {
349    fn from(id: SmartString) -> Self {
350        Self(id)
351    }
352}
353
354impl From<&str> for ClientId {
355    fn from(id: &str) -> Self {
356        Self(id.into())
357    }
358}
359
360impl From<ClientId> for SmartString {
361    fn from(id: ClientId) -> Self {
362        id.0
363    }
364}
365
366impl FromStr for ClientId {
367    type Err = std::convert::Infallible;
368
369    fn from_str(s: &str) -> Result<Self, Self::Err> {
370        Ok(Self(s.into()))
371    }
372}
373
374#[cfg(test)]
375mod tests {
376    use super::*;
377
378    #[test]
379    fn test_order_id_for_testing() {
380        let test_id = OrderId::for_testing();
381        assert_eq!(test_id.to_string(), "00000000-0000-0000-0000-000000000000");
382        assert_eq!(test_id.as_uuid(), &Uuid::nil());
383    }
384
385    #[test]
386    fn test_trade_id_for_testing() {
387        let test_id = TradeId::for_testing();
388        assert_eq!(test_id.to_string(), "00000000-0000-0000-0000-000000000000");
389        assert_eq!(test_id.as_uuid(), &Uuid::nil());
390    }
391
392    #[test]
393    fn test_position_id_for_testing() {
394        let test_id = PositionId::for_testing();
395        assert_eq!(test_id.to_string(), "00000000-0000-0000-0000-000000000000");
396        assert_eq!(test_id.as_uuid(), &Uuid::nil());
397    }
398
399    #[test]
400    fn test_production_ids_are_different() {
401        let id1 = OrderId::new();
402        let id2 = OrderId::new();
403        assert_ne!(id1, id2);
404        assert_ne!(id1.to_string(), "00000000-0000-0000-0000-000000000000");
405        assert_ne!(id2.to_string(), "00000000-0000-0000-0000-000000000000");
406    }
407}