Skip to main content

projectx_client/
ids.rs

1// SPDX-FileCopyrightText: 2026 Kevin Monaghan
2// SPDX-License-Identifier: MIT-0
3
4//! Validated provider identifier types.
5
6use std::{fmt, str::FromStr};
7
8use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as _};
9
10use crate::Error;
11
12macro_rules! numeric_id {
13    ($name:ident, $kind:literal, $repr:ty) => {
14        #[doc = concat!("Validated ProjectX ", $kind, ".")]
15        #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
16        #[repr(transparent)]
17        pub struct $name($repr);
18
19        impl $name {
20            #[doc = concat!("Creates a ProjectX ", $kind, ".")]
21            ///
22            /// # Errors
23            ///
24            /// Returns an error unless the provider identifier is positive.
25            pub fn new(value: $repr) -> Result<Self, Error> {
26                if value <= 0 {
27                    return Err(Error::InvalidIdentifier {
28                        kind: $kind,
29                        reason: "value must be positive",
30                    });
31                }
32                Ok(Self(value))
33            }
34
35            #[doc = concat!("Returns the raw ProjectX ", $kind, ".")]
36            #[must_use]
37            pub const fn get(self) -> $repr {
38                self.0
39            }
40        }
41
42        impl TryFrom<$repr> for $name {
43            type Error = Error;
44
45            fn try_from(value: $repr) -> Result<Self, Self::Error> {
46                Self::new(value)
47            }
48        }
49
50        impl fmt::Display for $name {
51            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52                self.0.fmt(f)
53            }
54        }
55
56        impl Serialize for $name {
57            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
58            where
59                S: Serializer,
60            {
61                self.0.serialize(serializer)
62            }
63        }
64
65        impl<'de> Deserialize<'de> for $name {
66            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
67            where
68                D: Deserializer<'de>,
69            {
70                let raw = <$repr>::deserialize(deserializer)?;
71                Self::new(raw).map_err(D::Error::custom)
72            }
73        }
74    };
75}
76
77macro_rules! string_id {
78    ($name:ident, $kind:literal) => {
79        #[doc = concat!("Validated ProjectX ", $kind, ".")]
80        #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
81        #[repr(transparent)]
82        pub struct $name(String);
83
84        impl $name {
85            #[doc = concat!("Creates a ProjectX ", $kind, ".")]
86            ///
87            /// # Errors
88            ///
89            /// Returns an error for an empty identifier or one containing
90            /// whitespace or control characters.
91            pub fn new(value: impl Into<String>) -> Result<Self, Error> {
92                let value = value.into();
93                if value.is_empty()
94                    || value
95                        .chars()
96                        .any(|character| character.is_whitespace() || character.is_control())
97                {
98                    return Err(Error::InvalidIdentifier {
99                        kind: $kind,
100                        reason: "value must be non-empty and contain no whitespace or control characters",
101                    });
102                }
103                Ok(Self(value))
104            }
105
106            #[doc = concat!("Borrows the raw ProjectX ", $kind, ".")]
107            #[must_use]
108            pub fn as_str(&self) -> &str {
109                &self.0
110            }
111        }
112
113        impl AsRef<str> for $name {
114            fn as_ref(&self) -> &str {
115                self.as_str()
116            }
117        }
118
119        impl FromStr for $name {
120            type Err = Error;
121
122            fn from_str(value: &str) -> Result<Self, Self::Err> {
123                Self::new(value)
124            }
125        }
126
127        impl TryFrom<String> for $name {
128            type Error = Error;
129
130            fn try_from(value: String) -> Result<Self, Self::Error> {
131                Self::new(value)
132            }
133        }
134
135        impl fmt::Display for $name {
136            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137                self.0.fmt(f)
138            }
139        }
140
141        impl Serialize for $name {
142            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
143            where
144                S: Serializer,
145            {
146                serializer.serialize_str(&self.0)
147            }
148        }
149
150        impl<'de> Deserialize<'de> for $name {
151            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
152            where
153                D: Deserializer<'de>,
154            {
155                let raw = String::deserialize(deserializer)?;
156                Self::new(raw).map_err(D::Error::custom)
157            }
158        }
159    };
160}
161
162numeric_id!(AccountId, "account identifier", i32);
163numeric_id!(OrderId, "order identifier", i64);
164numeric_id!(PositionId, "position identifier", i32);
165numeric_id!(TradeId, "trade identifier", i64);
166string_id!(ContractId, "contract identifier");
167string_id!(SymbolId, "symbol identifier");