1use once_cell::sync::Lazy;
2use parking_lot::RwLock;
3use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize, Serializer};
4use std::collections::HashMap;
5use std::fmt;
6use std::hash::Hash;
7use std::str::FromStr;
8
9const UNSPECIFIED_EXCHANGE_ID: u16 = 0;
10
11static EXCHANGES: Lazy<RwLock<ExchangeRegistry>> = Lazy::new(|| {
12 RwLock::new(ExchangeRegistry {
13 next_id: 1,
14 ..ExchangeRegistry::default()
15 })
16});
17
18static ASSETS: Lazy<RwLock<AssetRegistry>> = Lazy::new(|| RwLock::new(AssetRegistry::default()));
19static SYMBOLS: Lazy<RwLock<SymbolRegistry>> = Lazy::new(|| RwLock::new(SymbolRegistry::default()));
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
22pub struct ExchangeId(u16);
23
24impl ExchangeId {
25 pub const UNSPECIFIED: Self = Self(UNSPECIFIED_EXCHANGE_ID);
26
27 #[must_use]
28 pub const fn from_raw(value: u16) -> Self {
29 Self(value)
30 }
31
32 #[must_use]
33 pub const fn as_raw(self) -> u16 {
34 self.0
35 }
36
37 #[must_use]
38 pub fn is_specified(self) -> bool {
39 self.0 != UNSPECIFIED_EXCHANGE_ID
40 }
41
42 pub fn register(name: impl AsRef<str>) -> Self {
43 let name = canonicalize(name.as_ref());
44 if name.is_empty() {
45 return Self::UNSPECIFIED;
46 }
47 let mut registry = EXCHANGES.write();
48 if let Some(id) = registry.name_to_id.get(&name) {
49 return *id;
50 }
51 let id = ExchangeId(registry.next_id);
52 registry.next_id = registry.next_id.saturating_add(1);
53 let stored = leak_string(name.clone());
54 registry.id_to_name.insert(id, stored);
55 registry.name_to_id.insert(name, id);
56 id
57 }
58
59 #[must_use]
60 pub fn name(self) -> &'static str {
61 if self == Self::UNSPECIFIED {
62 return "unspecified";
63 }
64 let registry = EXCHANGES.read();
65 registry
66 .id_to_name
67 .get(&self)
68 .copied()
69 .unwrap_or_else(|| leak_string(format!("exchange#{}", self.0)))
70 }
71}
72
73impl Default for ExchangeId {
74 fn default() -> Self {
75 Self::UNSPECIFIED
76 }
77}
78
79impl fmt::Display for ExchangeId {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 f.write_str(self.name())
82 }
83}
84
85impl FromStr for ExchangeId {
86 type Err = IdentifierParseError;
87
88 fn from_str(s: &str) -> Result<Self, Self::Err> {
89 let name = canonicalize(s);
90 if name.is_empty() {
91 return Err(IdentifierParseError::new("exchange", s));
92 }
93 let mut registry = EXCHANGES.write();
94 if let Some(id) = registry.name_to_id.get(&name) {
95 return Ok(*id);
96 }
97 let id = ExchangeId(registry.next_id);
98 registry.next_id = registry.next_id.saturating_add(1);
99 let stored = leak_string(name.clone());
100 registry.id_to_name.insert(id, stored);
101 registry.name_to_id.insert(name, id);
102 Ok(id)
103 }
104}
105
106impl Serialize for ExchangeId {
107 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
108 where
109 S: Serializer,
110 {
111 serializer.serialize_str(self.as_ref())
112 }
113}
114
115impl<'de> Deserialize<'de> for ExchangeId {
116 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
117 where
118 D: Deserializer<'de>,
119 {
120 let raw = String::deserialize(deserializer)?;
121 raw.parse().map_err(D::Error::custom)
122 }
123}
124
125impl From<&str> for ExchangeId {
126 fn from(value: &str) -> Self {
127 value.parse().unwrap_or(Self::UNSPECIFIED)
128 }
129}
130
131impl From<String> for ExchangeId {
132 fn from(value: String) -> Self {
133 value.parse().unwrap_or(Self::UNSPECIFIED)
134 }
135}
136
137impl From<&ExchangeId> for ExchangeId {
138 fn from(value: &ExchangeId) -> Self {
139 *value
140 }
141}
142
143impl AsRef<str> for ExchangeId {
144 fn as_ref(&self) -> &str {
145 self.name()
146 }
147}
148
149#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
150pub struct AssetId {
151 pub exchange: ExchangeId,
152 pub asset_id: u32,
153}
154
155impl AssetId {
156 pub const fn new(exchange: ExchangeId, asset_id: u32) -> Self {
157 Self { exchange, asset_id }
158 }
159
160 pub fn from_code(exchange: ExchangeId, code: impl AsRef<str>) -> Self {
161 let code = canonicalize_asset(code.as_ref());
162 if code.is_empty() {
163 return Self::unspecified();
164 }
165 let mut registry = ASSETS.write();
166 let key = (exchange, code.clone());
167 if let Some(existing) = registry.name_to_id.get(&key) {
168 return Self::new(exchange, *existing);
169 }
170 let next = registry
171 .next_per_exchange
172 .entry(exchange)
173 .and_modify(|id| *id = id.saturating_add(1))
174 .or_insert(1);
175 let id = *next;
176 registry.name_to_id.insert(key, id);
177 registry
178 .id_to_name
179 .insert((exchange, id), leak_string(code));
180 Self::new(exchange, id)
181 }
182
183 #[must_use]
184 pub fn code(&self) -> &'static str {
185 asset_code_lookup(self.exchange, self.asset_id)
186 }
187
188 pub const fn unspecified() -> Self {
189 Self::new(ExchangeId::UNSPECIFIED, 0)
190 }
191}
192
193impl Default for AssetId {
194 fn default() -> Self {
195 Self::unspecified()
196 }
197}
198
199impl fmt::Display for AssetId {
200 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201 write!(f, "{}:{}", self.exchange, self.code())
202 }
203}
204
205impl FromStr for AssetId {
206 type Err = IdentifierParseError;
207
208 fn from_str(s: &str) -> Result<Self, Self::Err> {
209 let (exchange, code) = split_identifier(s, "asset")?;
210 Ok(Self::from_code(exchange, code))
211 }
212}
213
214impl Serialize for AssetId {
215 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
216 where
217 S: Serializer,
218 {
219 serializer.serialize_str(self.as_ref())
220 }
221}
222
223impl<'de> Deserialize<'de> for AssetId {
224 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
225 where
226 D: Deserializer<'de>,
227 {
228 let value = String::deserialize(deserializer)?;
229 value.parse().map_err(D::Error::custom)
230 }
231}
232
233impl From<&str> for AssetId {
234 fn from(value: &str) -> Self {
235 value
236 .parse()
237 .unwrap_or_else(|_| Self::from_code(ExchangeId::UNSPECIFIED, value))
238 }
239}
240
241impl From<String> for AssetId {
242 fn from(value: String) -> Self {
243 AssetId::from(value.as_str())
244 }
245}
246
247impl From<&AssetId> for AssetId {
248 fn from(value: &AssetId) -> Self {
249 *value
250 }
251}
252
253impl AsRef<str> for AssetId {
254 fn as_ref(&self) -> &str {
255 self.code()
256 }
257}
258
259#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
260pub struct Symbol {
261 pub exchange: ExchangeId,
262 pub market_id: u32,
263}
264
265impl Symbol {
266 pub const fn new(exchange: ExchangeId, market_id: u32) -> Self {
267 Self {
268 exchange,
269 market_id,
270 }
271 }
272
273 pub fn from_code(exchange: ExchangeId, code: impl AsRef<str>) -> Self {
274 let code = code.as_ref().trim();
275 if code.is_empty() {
276 return Self::unspecified();
277 }
278 let normalized = code.to_uppercase();
279 let mut registry = SYMBOLS.write();
280 let key = (exchange, normalized.clone());
281 if let Some(existing) = registry.name_to_id.get(&key) {
282 return Self::new(exchange, *existing);
283 }
284 let next = registry
285 .next_per_exchange
286 .entry(exchange)
287 .and_modify(|id| *id = id.saturating_add(1))
288 .or_insert(1);
289 let id = *next;
290 registry.name_to_id.insert(key, id);
291 registry
292 .id_to_name
293 .insert((exchange, id), leak_string(normalized));
294 Self::new(exchange, id)
295 }
296
297 #[must_use]
298 pub fn code(&self) -> &'static str {
299 symbol_code_lookup(self.exchange, self.market_id)
300 }
301
302 pub const fn unspecified() -> Self {
303 Self::new(ExchangeId::UNSPECIFIED, 0)
304 }
305}
306
307impl Default for Symbol {
308 fn default() -> Self {
309 Self::unspecified()
310 }
311}
312
313impl fmt::Display for Symbol {
314 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315 write!(f, "{}:{}", self.exchange, self.code())
316 }
317}
318
319impl FromStr for Symbol {
320 type Err = IdentifierParseError;
321
322 fn from_str(s: &str) -> Result<Self, Self::Err> {
323 let (exchange, symbol) = split_identifier(s, "symbol")?;
324 Ok(Self::from_code(exchange, symbol))
325 }
326}
327
328impl Serialize for Symbol {
329 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
330 where
331 S: Serializer,
332 {
333 serializer.serialize_str(self.as_ref())
334 }
335}
336
337impl<'de> Deserialize<'de> for Symbol {
338 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
339 where
340 D: Deserializer<'de>,
341 {
342 let value = String::deserialize(deserializer)?;
343 value.parse().map_err(D::Error::custom)
344 }
345}
346
347impl From<&str> for Symbol {
348 fn from(value: &str) -> Self {
349 value
350 .parse()
351 .unwrap_or_else(|_| Self::from_code(ExchangeId::UNSPECIFIED, value))
352 }
353}
354
355impl From<String> for Symbol {
356 fn from(value: String) -> Self {
357 Symbol::from(value.as_str())
358 }
359}
360
361impl From<&Symbol> for Symbol {
362 fn from(value: &Symbol) -> Self {
363 *value
364 }
365}
366
367impl AsRef<str> for Symbol {
368 fn as_ref(&self) -> &str {
369 self.code()
370 }
371}
372
373#[derive(Debug, Clone)]
374pub struct IdentifierParseError {
375 msg: String,
376}
377
378impl IdentifierParseError {
379 fn new(kind: &str, raw: impl AsRef<str>) -> Self {
380 Self {
381 msg: format!("invalid {kind} identifier: '{}'", raw.as_ref()),
382 }
383 }
384}
385
386impl fmt::Display for IdentifierParseError {
387 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
388 f.write_str(&self.msg)
389 }
390}
391
392impl std::error::Error for IdentifierParseError {}
393
394#[derive(Default)]
395struct ExchangeRegistry {
396 name_to_id: HashMap<String, ExchangeId>,
397 id_to_name: HashMap<ExchangeId, &'static str>,
398 next_id: u16,
399}
400
401#[derive(Default)]
402struct AssetRegistry {
403 name_to_id: HashMap<(ExchangeId, String), u32>,
404 id_to_name: HashMap<(ExchangeId, u32), &'static str>,
405 next_per_exchange: HashMap<ExchangeId, u32>,
406}
407
408#[derive(Default)]
409struct SymbolRegistry {
410 name_to_id: HashMap<(ExchangeId, String), u32>,
411 id_to_name: HashMap<(ExchangeId, u32), &'static str>,
412 next_per_exchange: HashMap<ExchangeId, u32>,
413}
414
415fn canonicalize(name: &str) -> String {
416 name.trim().to_ascii_lowercase()
417}
418
419fn canonicalize_asset(code: &str) -> String {
420 code.trim().to_ascii_uppercase()
421}
422
423fn split_identifier<'a>(
424 value: &'a str,
425 kind: &'static str,
426) -> Result<(ExchangeId, &'a str), IdentifierParseError> {
427 let value = value.trim();
428 if value.is_empty() {
429 return Err(IdentifierParseError::new(kind, value));
430 }
431 if let Some((exchange, rest)) = value.split_once(':') {
432 let exchange = exchange.parse()?;
433 let rest = rest.trim();
434 if rest.is_empty() {
435 return Err(IdentifierParseError::new(kind, value));
436 }
437 Ok((exchange, rest))
438 } else {
439 Ok((ExchangeId::UNSPECIFIED, value))
440 }
441}
442
443fn asset_code_lookup(exchange: ExchangeId, id: u32) -> &'static str {
444 if id == 0 {
445 return "UNKNOWN";
446 }
447 let registry = ASSETS.read();
448 registry
449 .id_to_name
450 .get(&(exchange, id))
451 .copied()
452 .unwrap_or_else(|| leak_string(format!("asset#{}", id)))
453}
454
455fn symbol_code_lookup(exchange: ExchangeId, id: u32) -> &'static str {
456 if id == 0 {
457 return "UNKNOWN";
458 }
459 let registry = SYMBOLS.read();
460 registry
461 .id_to_name
462 .get(&(exchange, id))
463 .copied()
464 .unwrap_or_else(|| leak_string(format!("symbol#{}", id)))
465}
466
467fn leak_string(value: String) -> &'static str {
468 Box::leak(value.into_boxed_str())
469}