Skip to main content

nautilus_data/engine/
registry.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use super::{
17    AHashSet, ClientId, DataClientAdapter, DataEngine, FAILED, Venue, check_key_in_map,
18    check_key_not_in_map, check_predicate_true,
19};
20
21impl DataEngine {
22    #[must_use]
23    pub(crate) fn is_external_client(&self, client_id: ClientId) -> bool {
24        self.cache.borrow().is_external_data_client(&client_id)
25    }
26
27    /// Registers the `client` with the engine with an optional venue `routing`.
28    ///
29    ///
30    /// # Panics
31    ///
32    /// Panics if a client with the same client ID has already been registered.
33    pub fn register_client(&mut self, mut client: DataClientAdapter, routing: Option<Venue>) {
34        client.is_stopped = self.is_stopped;
35        let client_id = client.client_id();
36
37        check_key_not_in_map(&client_id, &self.clients, "client_id", "clients").expect(FAILED);
38
39        if let Some(routing) = routing {
40            self.routing_map.insert(routing, client_id);
41            log::debug!("Set client {client_id} routing for {routing}");
42        }
43
44        if client.venue.is_none() && self.default_client_id.is_none() {
45            self.default_client_id = Some(client_id);
46            log::debug!("Registered client {client_id} for default routing");
47        }
48
49        self.clients.insert(client_id, client);
50        log::debug!("Registered client {client_id}");
51    }
52
53    /// Deregisters the client for the `client_id`.
54    ///
55    /// # Panics
56    ///
57    /// Panics if the client ID has not been registered.
58    pub fn deregister_client(&mut self, client_id: &ClientId) {
59        check_key_in_map(client_id, &self.clients, "client_id", "clients").expect(FAILED);
60
61        if self.default_client_id.as_ref() == Some(client_id) {
62            self.default_client_id = None;
63        }
64
65        self.clients.shift_remove(client_id);
66        log::info!("Deregistered client {client_id}");
67    }
68
69    /// Registers the data `client` with the engine as the default routing client.
70    ///
71    /// When a specific venue routing cannot be found, this client will receive messages.
72    ///
73    /// # Warnings
74    ///
75    /// Any existing default routing client will be overwritten.
76    ///
77    /// # Panics
78    ///
79    /// Panics if a default client has already been registered.
80    pub fn register_default_client(&mut self, mut client: DataClientAdapter) {
81        client.is_stopped = self.is_stopped;
82        check_predicate_true(
83            self.default_client_id.is_none(),
84            "default client already registered",
85        )
86        .expect(FAILED);
87
88        let client_id = client.client_id();
89        self.clients.insert(client_id, client);
90        self.default_client_id = Some(client_id);
91        log::debug!("Registered default client {client_id}");
92    }
93
94    /// Marks an already-registered client as the default for fallback routing.
95    ///
96    /// # Errors
97    ///
98    /// Returns an error if no client is registered with the given ID, or a different
99    /// client is already the default.
100    pub fn set_default_client(&mut self, client_id: ClientId) -> anyhow::Result<()> {
101        if self.default_client_id.is_some_and(|id| id != client_id) {
102            anyhow::bail!("default client already registered");
103        }
104
105        if !self.clients.contains_key(&client_id) {
106            anyhow::bail!("No client registered with ID {client_id}");
107        }
108
109        self.default_client_id = Some(client_id);
110        log::debug!("Set client {client_id} as default");
111        Ok(())
112    }
113
114    /// Sets routing for a specific venue to a given client ID.
115    ///
116    /// # Errors
117    ///
118    /// Returns an error if the client ID is not registered, or the venue is already routed to a
119    /// different client.
120    pub fn register_venue_routing(
121        &mut self,
122        client_id: ClientId,
123        venue: Venue,
124    ) -> anyhow::Result<()> {
125        if !self.clients.contains_key(&client_id) {
126            anyhow::bail!("No client registered with ID {client_id}");
127        }
128
129        if let Some(existing_client_id) = self.routing_map.get(&venue)
130            && *existing_client_id != client_id
131        {
132            anyhow::bail!(
133                "Venue {venue} already routed to {existing_client_id}, \
134                 cannot re-route to {client_id}"
135            );
136        }
137
138        self.routing_map.insert(venue, client_id);
139        log::debug!("Set client {client_id} routing for {venue}");
140        Ok(())
141    }
142
143    /// Returns connection status for each registered client.
144    #[must_use]
145    pub fn client_connection_status(&self) -> Vec<(ClientId, bool)> {
146        self.get_clients()
147            .into_iter()
148            .map(|client| (client.client_id(), client.is_connected()))
149            .collect()
150    }
151
152    /// Returns a list of all registered client IDs, including the default client if set.
153    #[must_use]
154    pub fn registered_clients(&self) -> Vec<ClientId> {
155        self.get_clients()
156            .into_iter()
157            .map(|client| client.client_id())
158            .collect()
159    }
160
161    pub(crate) fn collect_subscriptions<F, T>(&self, get_subs: F) -> Vec<T>
162    where
163        F: Fn(&DataClientAdapter) -> &AHashSet<T>,
164        T: Clone,
165    {
166        self.get_clients()
167            .into_iter()
168            .flat_map(get_subs)
169            .cloned()
170            .collect()
171    }
172
173    #[must_use]
174    pub fn get_clients(&self) -> Vec<&DataClientAdapter> {
175        self.clients.values().collect()
176    }
177
178    #[must_use]
179    pub fn get_clients_mut(&mut self) -> Vec<&mut DataClientAdapter> {
180        self.clients.values_mut().collect()
181    }
182
183    pub fn get_client(
184        &mut self,
185        client_id: Option<&ClientId>,
186        venue: Option<&Venue>,
187    ) -> Option<&mut DataClientAdapter> {
188        if let Some(client_id) = client_id {
189            return self.clients.get_mut(client_id);
190        }
191
192        if let Some(v) = venue
193            && let Some(client_id) = self.routing_map.get(v)
194        {
195            return self.clients.get_mut(client_id);
196        }
197
198        self.get_default_client()
199    }
200
201    /// Resolves the client for a subscribe/unsubscribe command.
202    ///
203    /// When `BACKTEST` is registered, all commands route through it regardless of
204    /// the command's `client_id` or `venue`. Request paths skip this override.
205    pub(super) fn get_command_client(
206        &mut self,
207        client_id: Option<&ClientId>,
208        venue: Option<&Venue>,
209    ) -> Option<&mut DataClientAdapter> {
210        let backtest_id = ClientId::new("BACKTEST");
211        if self.clients.contains_key(&backtest_id) {
212            return self.clients.get_mut(&backtest_id);
213        }
214
215        self.get_client(client_id, venue)
216    }
217
218    fn get_default_client(&mut self) -> Option<&mut DataClientAdapter> {
219        match self.default_client_id {
220            Some(id) => self.clients.get_mut(&id),
221            None => None,
222        }
223    }
224}