1use std::{
19 collections::HashMap,
20 sync::{LazyLock, OnceLock},
21};
22
23use parking_lot::Mutex;
24
25use crate::identifiers::Venue;
26
27static CBCM_LOCK: OnceLock<Venue> = OnceLock::new();
28static GLBX_LOCK: OnceLock<Venue> = OnceLock::new();
29static NYUM_LOCK: OnceLock<Venue> = OnceLock::new();
30static XCBT_LOCK: OnceLock<Venue> = OnceLock::new();
31static XCEC_LOCK: OnceLock<Venue> = OnceLock::new();
32static XCME_LOCK: OnceLock<Venue> = OnceLock::new();
33static XFXS_LOCK: OnceLock<Venue> = OnceLock::new();
34static XNYM_LOCK: OnceLock<Venue> = OnceLock::new();
35
36impl Venue {
37 #[allow(non_snake_case)]
39 pub fn CBCM() -> Self {
40 *CBCM_LOCK.get_or_init(|| Self::from("CBCM"))
41 }
42 #[allow(non_snake_case)]
44 pub fn GLBX() -> Self {
45 *GLBX_LOCK.get_or_init(|| Self::from("GLBX"))
46 }
47 #[allow(non_snake_case)]
49 pub fn NYUM() -> Self {
50 *NYUM_LOCK.get_or_init(|| Self::from("NYUM"))
51 }
52 #[allow(non_snake_case)]
54 pub fn XCBT() -> Self {
55 *XCBT_LOCK.get_or_init(|| Self::from("XCBT"))
56 }
57 #[allow(non_snake_case)]
59 pub fn XCEC() -> Self {
60 *XCEC_LOCK.get_or_init(|| Self::from("XCEC"))
61 }
62 #[allow(non_snake_case)]
64 pub fn XCME() -> Self {
65 *XCME_LOCK.get_or_init(|| Self::from("XCME"))
66 }
67 #[allow(non_snake_case)]
69 pub fn XFXS() -> Self {
70 *XFXS_LOCK.get_or_init(|| Self::from("XFXS"))
71 }
72 #[allow(non_snake_case)]
74 pub fn XNYM() -> Self {
75 *XNYM_LOCK.get_or_init(|| Self::from("XNYM"))
76 }
77}
78
79pub static VENUE_MAP: LazyLock<Mutex<HashMap<&str, Venue>>> = LazyLock::new(|| {
81 let mut map = HashMap::new();
82 map.insert(Venue::CBCM().inner().as_str(), Venue::CBCM());
83 map.insert(Venue::GLBX().inner().as_str(), Venue::GLBX());
84 map.insert(Venue::NYUM().inner().as_str(), Venue::NYUM());
85 map.insert(Venue::XCBT().inner().as_str(), Venue::XCBT());
86 map.insert(Venue::XCEC().inner().as_str(), Venue::XCEC());
87 map.insert(Venue::XCME().inner().as_str(), Venue::XCME());
88 map.insert(Venue::XFXS().inner().as_str(), Venue::XFXS());
89 map.insert(Venue::XNYM().inner().as_str(), Venue::XNYM());
90 Mutex::new(map)
91});
92
93#[cfg(test)]
94mod tests {
95 use rstest::*;
96
97 use super::*;
98
99 #[rstest]
100 #[case::cbcm(Venue::CBCM, "CBCM")]
101 #[case::glbx(Venue::GLBX, "GLBX")]
102 #[case::nyum(Venue::NYUM, "NYUM")]
103 #[case::xcbt(Venue::XCBT, "XCBT")]
104 #[case::xcec(Venue::XCEC, "XCEC")]
105 #[case::xcme(Venue::XCME, "XCME")]
106 #[case::xfxs(Venue::XFXS, "XFXS")]
107 #[case::xnym(Venue::XNYM, "XNYM")]
108 fn test_venue_constant(#[case] constructor: fn() -> Venue, #[case] expected: &'static str) {
109 let first = constructor();
110 let second = constructor();
111 let venue_map = VENUE_MAP.lock();
112
113 assert_eq!(first, second);
114 assert_eq!(first.inner(), expected);
115 assert_eq!(first.to_string(), expected);
116 assert_eq!(venue_map.get(expected), Some(&first));
117 }
118
119 #[rstest]
120 fn test_venue_constants_are_unique() {
121 let venues = all_venues();
122
123 for (i, venue) in venues.iter().enumerate() {
124 assert!(!venues[i + 1..].contains(venue), "duplicate venue {venue}");
125 }
126 }
127
128 #[rstest]
129 fn test_venue_map_has_expected_size() {
130 let venue_map = VENUE_MAP.lock();
131
132 assert_eq!(venue_map.len(), 8);
133 }
134
135 #[rstest]
136 #[case("INVALID")]
137 #[case("")]
138 #[case("NYSE")]
139 fn test_venue_map_lookup_returns_none(#[case] value: &str) {
140 let venue_map = VENUE_MAP.lock();
141
142 assert_eq!(venue_map.get(value), None);
143 }
144
145 #[rstest]
146 #[expect(clippy::needless_collect)] fn test_venue_constants_thread_safety() {
148 use std::thread;
149
150 let handles: Vec<_> = (0..4).map(|_| thread::spawn(all_venues)).collect();
151
152 let results: Vec<[Venue; 8]> = handles.into_iter().map(|h| h.join().unwrap()).collect();
153
154 for venues in &results {
155 assert_eq!(*venues, all_venues());
156 }
157 }
158
159 #[rstest]
160 #[expect(clippy::needless_collect)] fn test_venue_map_thread_safety() {
162 use std::thread;
163
164 let handles: Vec<_> = (0..4)
165 .map(|_| {
166 thread::spawn(|| {
167 let venue_map = VENUE_MAP.lock();
168 venue_map.get("XCME").copied()
169 })
170 })
171 .collect();
172
173 let results: Vec<Option<Venue>> = handles.into_iter().map(|h| h.join().unwrap()).collect();
174
175 for result in results {
177 assert_eq!(result, Some(Venue::XCME()));
178 }
179 }
180
181 fn all_venues() -> [Venue; 8] {
182 [
183 Venue::CBCM(),
184 Venue::GLBX(),
185 Venue::NYUM(),
186 Venue::XCBT(),
187 Venue::XCEC(),
188 Venue::XCME(),
189 Venue::XFXS(),
190 Venue::XNYM(),
191 ]
192 }
193}